🔥

Next.js v13 試してみる

2022/10/28に公開

モチベーション

Next.js v13出ましたね。
RFCで出ていた内容も多いので、アップデート内容が予測していた方もいらっしゃると思いますが、僕自身は詳しくコードをながめたことはなかったのでみていこうと思います

やること

  • Turbopack
  • New Next.js Router

やらないこと

  • next/image
  • @next/font
  • next/link

Turbopack(α)

Turbopackとは?

Rustで作られたJavaScript/TypeScriptのバンドラー
圧倒的なビルドスピードとキャッシュ機構が売り

試してみる

Turborepoのサイトにアクセスするとαバージョンのドキュメントがすでに用意されている

npx create-next-app --example with-turbopack

でサンプルアプリケーションが作られる

npm run dev

で開発環境が立ち上がる

Next.js v12でも SWC + Webpackの組み合わせで動いていたけど、今回のは体感できるぐらい速い
この後キャッシュビルドとかも入ってくると思うので楽しみ

New Next.js Router

appディレクトリ配下にファイルを置くことで適応可能

今まではpagesディレクトリにはデフォルトで一つしかファイルが置けなかったが、appディレクトリ関しては基本的には何をおいても良さそう。

そして、以下の5つが予約ファイルという扱いになっている。

  • layout.tsx
  • template.tsx
  • error.tsx
  • loading.tsx
  • page.tsx

ディレクトリ構成はこんな感じになるみたい

layout.tsx

配下のディレクトリで共有されるレイアウト
ヘッダー、フッター、サイドバーなどのコンポーネントを呼び出すのが良さそう
ページ遷移の際、再レンダリングがなく、つまり状態が保持される

export default function RootLayout({ children }: { children: React.ReactNode }) {
 ...
          <div className="col-start-2">
            <GlobalNav />
          </div>

          <div className="col-start-3 space-y-6">
            <AddressBar />

            <div className="rounded-xl border border-zinc-800 bg-black p-8">
              {children}
            </div>
          </div>
 ...

childrenにページやローディング、エラーが挿入されるんだと思う

template.tsx

layoutとすごくよく似ていて、共通のレイアウトを組むためのもの
ただ、layoutと違い、ページが切り替わったタイミングで再レンダリングを行う。

  • CSSのアニメーションを仕込みたい(マウント、アンマウントさせたい)
  • useEffectによるログ発火
  • etc..

サンプルではBoundaryというマウントされるとアニメーションが発火するコンポーネント呼び出しをしていた

app/layout/template.tsx
import { Boundary } from '@/ui/Boundary';
import React from 'react';

export default function Template({ children }: { children: React.ReactNode }) {
  return <Boundary>{children}</Boundary>;
}

error.tsx

errorがthrowされた時にレンダリングされる

loading.tsx

データがまだ整っていない状態にレンダリングされるもの

通常であれば、こんな感じでSuspenseのfallbackとしてローディングコンポーネントを流し、

<Suspense fallback={<Loading />}>{children}</Suspense>

children側でpromiseをthrowさせることでローディングコンポーネントを表示することになりそうだが、これをラップしてくれているみたい

なので、使用者はSuspenseを書くことなくloading.tsxを用意しておくだけで良いという感じになりそう。

ローディング状態であることについては、内部的にはSuspenseが使われているので、Promiseをthrowする形になりそう。

サンプルでは、噂のuseというhooksが使われており、以下のような形でpromiseを返す関数をuseで囲む形にしておけば良さそう

// @ts-ignore
import { use } from 'react';
import {
  fetchCategoryBySlug,
  PageProps,
  type Category,
} from '@/lib/getCategories';
import { SkeletonCard } from '@/ui/SkeletonCard';

const fetchCategory = async (
  categorySlug: string | undefined,
): Promise<Category | undefined> => {
  // artificial delay
  await new Promise((resolve) => setTimeout(resolve, 3000));

  if (!categorySlug) return;

  return await fetchCategoryBySlug(categorySlug);
};

export default function Page({ params }: PageProps) {
  const category = use(fetchCategory(params.categorySlug));
  if (!category) return null;

  return (
    <div className="space-y-4">
      <div className="text-xl font-medium text-zinc-500">{category.name}</div>

      <div className="grid grid-cols-3 gap-6">
        {Array.from({ length: category.count }).map((_, i) => (
          <SkeletonCard key={i} />
        ))}
      </div>
    </div>
  );
}

page.tsx

今までのページコンポーネントとほぼ同様
layoutやtemplate、error、loadingなどが切り分けられているので、その辺りの制御は意識すると良いかも

まとめ(感想)

Turbopackの発展が楽しみ

今は開発のみの利用ということでnext devでしかturboオプションが用意されてないですが、かなり速くなる印象だったので、リモートキャッシュも使えるようになってきたりしたらかなりいい感じになるんじゃないだろうかと思ってます。

New Next.js Router 難しそうだけど、必要な改善だと思う

スケールさせていく時にページコンポーネントがどうしても肥大化していったり、_app.tsxを切り分けたいなぁと思う瞬間は多々あったし、ローディング制御したくないなぁもよく感じていたので、良い改善だと感じました。

サーバーコンポーネントあたりの理解がまだ及んでないので、その辺りも勉強しないとなと改めて感じました。

Discussion