Next.js v13 試してみる
モチベーション
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というマウントされるとアニメーションが発火するコンポーネント呼び出しをしていた
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