Essay 2025.11.24

Next.jsからSvelteへ移行した話

自作ウェブサイト

プログラミングの学習とセルフブランディングのため、自分の個人ウェブサイトを一から作りながら多くの試行錯誤を重ねています。AIが溢れる今の時代、開発者自らが手でウェブサイトを最初から最後まで作る機会は徐々に減っています。しかし、AIを積極的に活用するにしても、それを監督する人が基礎をきちんと理解していなければAIに正確な要求を伝えるのは難しいと考えました。だからこそ、こうした時代だからこそ自分でコードを書き、AIに具体的で適切な指示を出せる力を養うことが重要だと感じています。

当初は周囲で聞いた話だけを頼りに手当たり次第サイトを作ってみたため、試行錯誤が多くありました。それでも実際にサービスをデプロイして運用する中で、Webサービス開発の全工程を体で覚えることができ、とても貴重な経験になりました。余暇にコードを書き、個人の持ち出しでサーバー費用を負担するプロジェクトであるがゆえに、華やかなデザインや大規模なインフラを整えるのは難しかったです。その代わり、限られた資源の中でいかに効率的にシステム設計と構築を行うかを考えるようになり、自然と関連分野の学習にも深く没頭するようになりました。


Next.jsで開発開始

最初は「開発の学習」そのものが重要な目標だったため、多くの人に検証された技術スタックを中心にウェブサイトを作ることにしました。ウェブ制作で広く使われているフレームワークであるNext.jsを選べば、関連情報をインターネット上で容易に見つけられるだろうと判断したからです。そうして迷わずNext.jsを導入し、ページの開発を始めました。ChatGPTなどのAIツールやGoogle検索がそばにあったため、当時は恐れるものはないと感じていました。

しかし時間が経つにつれて、Next.jsベースのコードがだんだん複雑になる印象を受けました。ロジックが増え構造が複雑になるほど、useMemoのような最適化手法を適切に使う必要があり、機能実装とパフォーマンス最適化を同時に考えることが徐々に大きな負担になってきました。さらにReactとNext.jsは短期間で次々と新機能を追加していくため、その変化に追いつくために必要な時間と労力も増えていくように感じました。もちろんReactのエコシステムをより深く理解すれば十分に克服できる課題だとは思いました。しかし職場での業務が忙しくなるにつれて余暇の時間が減り、実装したいアイデアや機能はむしろ増えていきました。結果として、自分が投入できる時間には明確な限界があることを痛感しました。


Svelteとの出会い

そんな折、インターネットで情報を探しているうちに偶然Svelteというフレームワークに出会いました。SvelteはReactのように仮想DOMを使うのではなく、コンパイル時に効率的なJavaScriptコードへ変換してくれる点が印象的でした。そのおかげでフロントエンドアプリケーションが軽くなり、コードもより簡潔になり得るという説明が特に魅力的に感じられました。

Next.jsにある程度疲れを感じていたこともあり、自然とSvelteの公式ドキュメントやチュートリアルを素早く読み進めました。ReactやNext.jsと比べて提供される関数や機能の数は確かに少ないですが、その分シンプルさによって頭の中のアイデアを実装するには十分だと感じました。結局、Next.jsで書いていた既存のコードベースをSvelteベースに移行することに決め、本格的にリファクタリング作業に取りかかりました。


Svelteの魅力:簡潔なコード

最近のウェブ開発ではReactやNext.jsのようなコンポーネントベースのフレームワークが標準のように使われています。これらのツールは強力ですが、状態管理やイベント処理だけでもimport、フック、コンポーネント関数の宣言など、かなりの量のボイラープレートコードが伴います。Svelteは同じことをより少ないコードと直感的な文法で解決することを目指しています。画面上の数値を1つ増やすだけの簡単なカウンターを例にしても、その違いは明らかです。

以下の例は、まったく同じ機能を持つカウンターを2つの方法で実装したコードです。上はReact + Next.jsの例で、useStateをimportし、コンポーネント関数を作り、状態を更新する関数を別に定義する必要があります。下はSvelte + SvelteKitの例で、<script>内で数字の変数を1つ宣言(let count = $state(0))し、ボタンにonclickイベントをつなぎ、画面には{count}をそのまま出力すれば完了です。ちなみにNext.jsでHookを使う場合、CSRコードとSSRコードを分けて別々に書き、後で統合するような設計にする必要がありましたが、SvelteではSSRでもRune(ReactのHookに似た概念)を自由に使えるため、状態管理のために長いボイラープレートを書かなくて済む点が非常によかったです。


簡単なカウンター (React + Next.js)

"use client"

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(prev => prev + 1);
  };

  return (
    <button onClick={handleClick}>
      Count: {count}
    </button>
  );
}
javascript


簡単なカウンター (Svelte + SvelteKit)

<script>
  let count = $state(0);

  const handleClick = () => {
    count += 1;
  };
</script>

<button onclick={handleClick}>
  Count: {count}
</button>
javascript


この簡単なカウンターの例は「数値をどう管理するか」を示すものに過ぎませんが、実際のサービスではユーザーの入力に応じて別の値が自動的に変わるケースがはるかに多いです。たとえば名前入力欄に「Sung-eon」と入力すると、すぐ下の挨拶文が Hello, Sung-eon! に変わる画面を想像すれば分かりやすいでしょう。

このとき画面には2つの値が登場します。1つはユーザーが直接入力する名前(name)、もう1つはその名前に基づいて作られる挨拶(greeting)です。挨拶は常に名前によって決まるため、こうした値は派生状態(derived state)と呼ばれます。「Aという値が変わればBという値もそれに応じて自動的に変わる」という関係をコードでどう表現するかがポイントです。

React/Next.jsでは通常、名前をuseStateで管理し、挨拶はuseMemoやレンダリング関数内の計算ロジックで処理します。機能的には問題ありませんが、フックのimportや依存配列の管理、入力イベントからevent.target.valueを取り出すなど副次的なコードがかなり付随します。以下では同じ機能をSvelteで実装したときにどのように変わるか、そしてSvelte 5では$stateと$derivedを使って「この値は状態で、こちらの値はその状態から自動的に計算される」という関係をより直接的に表現できるかを例で示します。


状態の更新 (React + Next.js)

'use client';

import { useMemo, useState } from 'react';

export default function Greeting() {
  const [name, setName] = useState('');

  const greeting = useMemo(() => name ? `Hello, ${name}!` : 'Hello, world!', [name]);

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setName(event.target.value);
  };

  return (
    <>
      <input
        value={name}
        onChange={handleChange}
        placeholder="Enter your name"
      />
      <p>{greeting}</p>
    </>
  );
}
javascript


状態の更新 (Svelte + SvelteKit)

<script>
  let name = $state('');

  let greeting = $derived(name ? `Hello, ${name}!` : 'Hello, world!');
</script>

<input bind:value={name} placeholder="Enter your name" />
<p>{greeting}</p>
javascript


Svelteのコードを見れば、Reactの例と同様に「入力値に応じて挨拶が変わる」機能を維持しつつ、はるかに少ない要素で同じ流れを表現していることが分かります。名前は$state('')の一行で状態であることを示し、挨拶は$derived(...)で「この値はnameによって自動的に計算される」と宣言しておけばよいのです。別途フックのimportや依存配列、イベントオブジェクトからevent.target.valueを取り出すようなボイラープレートがなくても、入力値と派生状態の関係がコードレベルで直ちに読み取れるため、機能は同じでも「何がいつ、何によって変わるか」を理解する負担はずっと軽くなります。


Svelte移行の障壁:エコシステム

Svelteを導入する際にしばしば指摘される弱点の一つはエコシステムの規模です。Reactと比べて参照できるサンプルやライブラリ、コミュニティ資料が少ない点が大きなデメリットとして語られがちです。しかし少なくとも今回私が進めた個人ウェブサイトプロジェクトの範囲では、それが大きな問題として体感されることはあまりありませんでした。TiptapのようなWYSIWYGエディタやAuth.jsのような認証ライブラリもSvelteを公式サポートしており、「Svelteだから使えないツールが多い」という印象は思ったよりも強くありませんでした。むしろ必要な機能を実装する際にライブラリ不足で詰まる経験はほとんどありませんでした。

ただし今回のプロジェクトはあくまで個人の学習とポートフォリオ作成が目的だったため、このように感じられた面もあると思います。実際のサービス運用環境では、チーム内の開発者の熟練度や社内の標準スタック、長期的な保守性の観点からSvelteのエコシステム規模がどの程度制約になるかはまだ容易には想像できません。現時点では「個人プロジェクトや実験的な用途では十分魅力的だが、企業導入に際してはもう少し慎重な検討が必要だろう」という整理が妥当だと考えています。


まとめ

今回の個人ウェブサイトプロジェクトを通して感じたのは、結局のところ重要なのは「ReactかSvelteか」といった単純な技術選択そのものではなく、ツールを実際に使って長所と短所を自分の体で理解するプロセスだという点です。Next.jsを使っている間は複雑な構造と急速に変化するエコシステムの中で時間の制約を強く感じ、Svelteに移ってからはより簡潔な文法と軽いコードによってコーディング効率が向上する体験を得ました。

もちろんSvelteが万能の答えだとは思っていません。実務環境ではチーム構成、既存コードベース、採用市場、長期保守など、個人プロジェクトでは直面しない制約が多数存在します。ただ今回の経験を通じて、単に流行っている技術だからと追随するのではなく、自分で作って試しながら自分や作りたいサービスにより適したツールを選ぶための判断基準が少しは得られたと感じています。

AIがコードを代わりに書いてくれる時代だとしても、どのアーキテクチャを選ぶか、どのトレードオフを受け入れるかを決めるのは最終的に人間です。今後も私は個人ウェブサイトを小さな実験場として、新しいフレームワークやライブラリを試し続け、「AIに何をどうさせるか」を自分で設計できる開発者へと成長していきたいと思います。


Loading reactions and comments…

© 2026 Sung-eon Space

Sung-eon Space

Essays, photographs and small tools by Park Sung-eon. Written in Tokyo.

© 2026 Park Sung-eon. All rights reserved.