Cloudflare PagesでNext.jsのEdge Runtimeがサポートされました

Cloudflare PagesでNext.jsのEdge Runtimeがサポートされました。このサポートによりNext.jsのSSR、Middleware、API RouteがCloudflare Pagesで利用できるようになりました。
2022.10.25

はじめに

こんにちは、今夜のNext.js Conf 2022が楽しみなCX事業本部Delivery部MADグループの森茂です。(執筆時点2022/10/25)

2022/10/24よりCloudflare PagesでNext.jsのEdge Runtimeがサポートされました。
今までは静的なファイルへ書き出されたサイトのみがサポートされていましたが、このアップデートによりNext.jsのEdge Runtimeを利用したEdge環境での動的な処理がサイトで利用できるようになっています。

2022/10/26追記
この記事はNext.js v12.3.1での検証内容となります。Next.js v13ではまだビルドが通らないようでした。

注意が必要な点としては、Cloudflare Workersで動作するEdge RuntimeであるためNode.js環境に依存したものなどライブラリによって動作しない場合があります。Cloudflareの制限というよりはEdge Runtimeの特色となります。そのためNode.js環境で動かしているSSRを利用している既存の環境をそのまま移行ということは難しいかもしれません。

2022/10/24現在 Next.jsのEdge RuntimeはExperimentalとなっています。

ためしてみた

さっそくNext.jsの新規プロジェクトを作成してためしてみます。

$ npx create-next-app@latest sample-app --ts

API Routeへの実装

まずはAPI Routeから実装をためしてみます。
API Routeごとにexperimental-edgeオプションを追加することでEdge Runtimeを利用できます。

export const config = {
  runtime: 'experimental-edge',
}

Next.jsのAPI Routeの例

/pages/api/hello.ts

import type { NextApiResponse } from 'next';

export default function handler(
  res: NextApiResponse
) {
  res.status(200).json({ name: 'John Doe' });
}

Next.js Edge Runtime利用のAPI Route例

/pages/api/hello-edge.ts

import type { NextResponse } from 'next/server';

export const config = {
  runtime: 'experimental-edge',
};

export default function handler(es: NextResponse) {
  return new Response(JSON.stringify({ name: 'John Doe' }), {
    status: 200,
    headers: {
      'content-type': 'application/json',
    },
  });
}

従来のAPI RouteとEdge Runtimeでは記述が大きく変わります。Cloudflare Workers等利用されている方には違和感はないと思いますが、Web APIの沿った書き方が必要となってきます。

ServerSideProps

つづけてSSRの動作もためしてみます。
next.config.tsファイルに設定を追記することで、ページごとに記載することなくすべてのページを有効化できます。

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    runtime: 'experimental-edge',
  },
  reactStrictMode: true,
  swcMinify: true,
}

module.exports = nextConfig

Edge Runtimeっぽいロジックは何もないですが、SSRの動作確認のためServerSidePropsからfetchしたデータをSSRするページを用意しておきます。

/pages/index.tsx

import type {
  GetServerSideProps,
  InferGetServerSidePropsType,
  NextPage,
} from 'next';

type Props = InferGetServerSidePropsType<typeof getServerSideProps>;

type Todo = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};

const Home: NextPage<Props> = (props) => {
  const todo: Todo[] = props.todo;
  return (
    <div>
      <h1>Todo</h1>
      <ul>
        {todo.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default Home;

export const getServerSideProps: GetServerSideProps = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos');
  const todo = await response.json();
  return {
    props: {
      todo,
    },
  };
};

Middleware

つづいてMiddlewareも簡単なものを用意しておきます。レスポンスヘッダーにx-modified-edgeというカスタムヘッダーを追加するというものです。

import { NextResponse } from 'next/server';

export function middleware() {
  const response = NextResponse.next();
  response.headers.set('x-modified-edge', 'true');
  return response;
}

Edge Runtimeで利用できるMiddlewareについては下記に公式のサンプルも多く用意されているので参照ください。

以上で下準備は完了です。

Cloudflare Pagesへのデプロイ

準備ができたところでCloudflare Pagesへデプロイしてみます。あらかじめCloudflareのアカウントとGitHub連携のデプロイをするためリポジトリを用意しておく必要があります。

Cloudflare dashboard

すでにCloudflare PagesのプロファイルにNext.jsが追加されているので、リポジトリの連携とプロファイルを選択するだけでビルド コマンドビルド出力ディレクトリには推奨設定が適応されます。

ビルド&デプロイの設定項目 設定値
本番環境のブランチ main
ビルド コマンド npx @cloudflare/next-on-pages --experimental-minify
ビルド出力ディレクトリ .vercel/output/static

なお、Next.jsのデプロイにはNode.js v14以上が必要となります。環境変数でNODE_VERSIONに14以上を設定するか、プロジェクトディレクトリに.node-versionを追加してv14以上のバージョンを指定しおく必要があります。

また、2022/11/30までの限定対応ですが、設定>関数のCompatibility flagsに下記2種類を設定しておく必要もあります。

  • streams_enable_constructors
  • transformstream_enable_standard_constructor

詳細については下記公式ドキュメントも参照ください。

動作の確認

準備もできたところでさっそくデプロイして検証してみます。

Middlewareの動作確認

ローカル環境では問題がなかったのですが、Cloudflare Pagesへデプロイした際にレスポンスの中身が空になってしまう現象が発生しページの表示ができなくなってしまいました。付与されるHeaderは追加されているためMiddlewareについては改めて検証してみようと思います。

22/10/26追記
Vercelへ同じソースコードをデプロイしたところVercel上ではMiddlewareの動作に問題なさそうでした。

以降の検証についてはMiddlewareを外した状態で行っています。

API Routeの確認

単にJSONを返すだけですが、問題なさそう。

Server Side Renderingの確認

JSON PlaceHolderから取得したデータがサーバーサイド側で用意されて問題なく返ってきているようです。

さいごに

さわりだけですが、Next.jsのEdge Runtimeに対応したCloudflare Pagesをためしてみました。実際に作り込んでいくとEdge RuntimeというNode.jsと異なった環境とはなるため構成するライブラリによっては動作しないものも数多くあると思います。とはいえコストパフォーマンスの高いCloudflare PagesでNext.jsのSSRやAPI Routeを利用できる選択肢が増えたことはうれしいところです(しかもEdge環境で!)。

KVやR2、DurableObjects、D1との連携も容易となるのでCloudflare Pagesで動作するNext.jsには今までとはまた違った活用法もたくさんありそうですね。