今更ながらNext.jsに入門した

こんにちは丸山@h13i32maruです。すごい今更ながらNext.jsに入門したので感想をメモしておこうと思います。

入門資料

@hokacchaさんがクックパッド社のインターンシップ用に公開している動画を見て入門しました。要点がコンパクトにまとまってるので、これを見れば基本は理解できるようになってよかったです。

SSG

CodeLunch.fmのサイトをNext.jsのSSGで作り直しました。もともとreact-dom/serverrenderToString()を使ってなんちゃってSSGを自前で実装していたので、移行は簡単でした。getStaticPropsgetStaticPathsの仕組みはうまいこと考えられてていいな〜と思いました。

それと、データを特に持っていない本当の静的サイトでもNext.js使ってSSGで作るのは良さそうと思いました。サイト内のパーツをReactコンポーネント化できるので。

TypeScript連携

特に何も考えず.tsxでファイルを作ってNext.jsを起動すれば、TypeScriptの使用を検知してtsconfig.jsonを吐き出してくれて何も設定いらずに使用できました。すごい快適。ただtsconfig.jsonstrictプロパティはtrueに変更したほうがよさそう。

あと、公式ドキュメントにもTypeScriptセクションがあって、TypeScriptでの書き方(主に型の付け方)がまとまっていて便利でした。

SSR

自分の家庭内で使う「子供の献立作成アプリ」を作るのにSSRを使ってみました。CSRでも良かったんですが、近々別のサービスでSSRを使う予定だったので。

SSGと同じ要領でgetServerSidePropsを実装すればSSRできるのはすごい簡単でした。Next.jsのSSGやSSRの抽象化の仕組みは仕様がシンプルなのに、やれることに制限がなくていい設計だなぁと思いました。SSR/SSGをする他のフレームワークを使った事ないので他と比較してどうかはわかりませんが。

サーバ/クライアント

SSRで実装していると、サーバ側で動くコードとクライアント側で動くコードが頭の中で混乱しました。pages/配下から呼び出されるコードはSSRするときにサーバ側でも動くし、クライアント側でも動く(ハイドレーションされる)というのは理解はできてても難しいですね。

このあたりは書いていくうちに慣れては来ましたが、たまに「あれ?なんでここエラーになるんだっけ??」みたいなのがあって「あ、これはサーバサイドでしか動かないコードだったわ」みたいなのがあります。

ディレクトリ構成

Next.jsではpages/pages/apiしかディレクトリの規則がないので、自由に構成することができます。そこでどんな構成がいいのかがちゃがちゃして今のところは以下の構成になってます。

./src
├── client
│   ├── Repo
│   └── Component
│       ├── Fragment
│       ├── Page
│       └── View
├── pages
│   └── api
├── server
│   ├── ServerRepo
│   └── Route
└── type

pages/にはコンポーネントの実態はおかずに、getServerSidePropsの実装に集中するようにしています。

// ----------------------------
// src/pages/FooPage.tsx
// ----------------------------
export default function page(props: FooPageProps) {
  return <FooPage {...props} />;
}

export const getServerSideProps: GetServerSideProps<FooPageProps> = async (context) => {
  return ...;
}

// ----------------------------
// src/client/Component/FooPage.tsx
// ----------------------------
export type FooPageProps = {...};
export const FooPage: React.FC<FooPageProps> = (props) => {
  return ...;
}

src/clientsrc/serverに分けたのはなかなか分かりやすくて良いんですが、SSRをしている都合上src/client/Component配下のコードはSSR時にも実行されます。もうちょっとしっくりくるディレクトリ構成がほしいな〜と思いました。

API Route

ものすごい素朴な感じですね。HTTPリクエストメソッドに応じたルーティング的なものもありません。ここはもうちょっと仕組みがデフォルトでほしいところです。でもexpress.js風なシンプルさは安心感がありました。

next/link

パラメータ定義をファイル名に埋め込んだルーティングは最初ちょっとびっくりしましたが、すぐに慣れました。そういう規約のおかげで、画面遷移もnext/linkを使って簡単に書けるのがよかったです。

ただ、遷移元で遷移先のパスを愚直に文字列で組み立てる方法だと、パスが変わったときやパラメータ定義を変えたときなどに困りますね。なので遷移先でURLを組み立てる関数を公開するとかの工夫をしたほうがよさそうだなと思いました。

あと、SSRだと画面遷移時にURLは変わりますが、ブラウザのローディングバーなどが動きません。これはHistory APIを使ってるからだと思います。その結果、画面遷移が体感的に遅く感じてしまいます。なので、pages/_app.tsxのなかでrouter.eventsを使い、画面遷移時に独自のローディングバーを表示するようにしました。

// src/pages/_app.tsx
export default function MyApp({ Component, pageProps }: AppProps) {
  const [loading, setLoading] = useState(false);
  const router = useRouter();

  useEffect(() => {
    router.events.on('routeChangeStart', () => {
      setLoading(true);
    });
    router.events.on('routeChangeComplete', () => {
      setLoading(false);
    });
  }, []);

  return (
    <>
      <Component {...pageProps} />
      <Loading enable={loading}/>
    </>
  );

Vercel

Vercelめっちゃ便利。デプロイが超簡単でした。Next.js使った簡単なアプリならこれで十分です。

ただし、問題はデータストレージです。今回作ったアプリは家庭内で使うだけのアプリなので、ファイルにJSONをただ書き出せれば良かったんですが、Vercelではファイルアクセスはできません(そもそもサーバレス)。かといって、RDBMSをどこかにたてて、そこにVercelからつなぐようにするのも面倒そうだなと思ったので、今回はGitHubのgistにデータを保存するようにしました。gistはGitHubのアクセストークンと@octokit/coreがあれば簡単に読み書きできます。まあレイテンシーとか処理時間とかは遅いですが。あとは毎回JSONをフルで読み書きする必要があるので、同時アクセスでぶっ壊れます。なので雑な排他制御を実装してお茶を濁しました。

Vercelにデータストレージもついていればまじで完璧なんだけどな〜。低スペックなRDBつけてくれないかな。sqliteベースでもいいので。

その他

.envが何もせず使えて便利でした。しかもVercelには環境変数を設定する機能もちゃんとついてて快適。

styled-componentsを使ったんですが、ちょっとだけ追加設定が必要でした。

https://github.com/vercel/next.js/tree/master/examples/with-styled-components

react-beautiful-dndを使うのにも少しだけ設定が必要でした。

https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/reset-server-context.md

まとめ

Next.jsはすごく快適に開発できてめちゃくちゃ良かったです。ReactなどのWebフロントエンドエコシステムとサーバサイドをいい感じに連携させるのは大変という課題をよく聞きます。Next.jsはその課題を、Webフロントエンド特にReactを開発の中心に持ってくることで解決しているんだなと思いました。

ただ、React/TSが初めての人が独習していくのは大変そうだな〜と感じました。まあこれはNext.jsやWebフロントエンドに限った話ではないと思いますが。

というわけで、久々に新しいツールにちゃんと入門して楽しかったです。

現職のUbie Discoveryで、Nuxt.jsからNext.jsへ移行した話とかもあるので興味ある方は是非。