編集

Next.js v13.3

Next.js v13.3 がリリース。

#ファイルベース Metadata API

Next.js v13.2 で追加されたコンフィグベースの Metadata API に加えて、ファイル規則によってメタデータをカスタマイズ出来るようになりました。この機能は、App Router でのみ利用できます。

次のように利用します。

plaintext
app
├── favicon.(ico|jpg|png|svg)
├── sitemap.(xml|js|jsx)
├── robots.(txt|js|jsx)
├── manifest.(json|js|jsx)
├── layout.js
├── page.js
└── about
    ├── opengraph-image.(jpg|png|svg)
    ├── twitter-image.(jpg|png|svg)
    └── page.js

配置された favicon、sitemap、 robots、manifest、opengraph-image、twitter-image は自動で head に反映されます。

動的なファイル生成

JS や JSX をサポートしている sitemap や robots、manifest では動的な生成が可能です。

tsx
export default async function sitemap() {
  const res = await fetch("https://.../posts");
  const allPosts = await res.json();

  const posts = allPosts.map((post) => ({
    url: `https://acme.com/blog/${post.slug}`,
    lastModified: post.publishedAt,
  }));

  const routes = ["", "/about", "/blog"].map((route) => ({
    url: `https://acme.com${route}`,
    lastModified: new Date().toISOString(),
  }));

  return [...routes, ...posts];
}

#動的な OGP 及び Twitter 画像生成

ファイルベース Metadata API における opengraph-image や twitter-image も動的な生成が可能です。拡張を js や jsx にして次のようにします。

tsx
// /app/about/opengraph-image.tsx
import { ImageResponse } from "next/server";

export const size = { width: 1200, height: 600 };
export const alt = "About Acme";
export const contentType = "image/png";

export default function og() {
  // 中身であるsatoriと同様の記述が出来ます。
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: "white",
          width: "100%",
          height: "100%",
          display: "flex",
          textAlign: "center",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    }
  );
}

#App Router の静的エクスポート

App Router を完全に静的なファイルとして出力できるようになりました。

tsx
**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  output: 'export',
};

module.exports = nextConfig;

静的エクスポートの場合、サーバーコンポーネントや動的な画像生成などは、ビルド中に実行されます。ただし、サーバーが必要な headers()や cookies()などは実行出来ません。

また、この変更により next export コマンドは不要になりました。

#Interception Routes

Interception routes は URL を変化させつつ、現在のレイアウトに新しいページを表示します。Interception routes では、相対パスの../を表す(..)や app からの相対パスを表す(…)を利用します。

次は Vercel が用意したサンプルアプリケーションの構成です。

plaintext
app
├── @modal
│   └── (..)photos
│       └── [id]
│           └── page.tsx
├── page.tsx
├── layout.tsx
└── photos
		└── [id]
		    └── page.tsx

ルートページには画像一覧があり、画像をクリックすると/app/@modal/(..)photos/[id]/page.tsx で定義された画像を子要素に持つモーダルが表示されますが、このとき URL は../photos/[id]になります。この状態でページをリロードすると、表示されるのは/app/photos/[id]/page.tsx の内容になります。

#Parallel Routes

Parallel routes は、同じレイアウトの中に複数のページを表示します。Interception Routes で紹介したサンプルアプリケーションでは、この機能によってルートページに画像一覧とモーダルを同時に表示しています。

Parallel routes は、Web components の slot をイメージすると良いと思います。ディレクトリ名の先頭@を付けて、名前付きのスロットとして定義して利用します。サンプルアプリケーションでは@modal がこれに相当します。

plaintext
app
├── @modal
│   └── (..)photos
│       └── [id]
│           └── page.tsx
├── page.tsx
├── layout.tsx
└── photos
		└── [id]
		    └── page.tsx

名前付きのスロットは同じ階層にある layout.js の props から利用します。サンプルアプリケーションでは app/layout.js が相当します。

jsx
export default function Layout(props) {
  return (
    <html>
      <body>
        <GithubCorner />
        {props.children}
        {props.modal}
      </body>
    </html>
  );
}

#参考文献

編集