👀

Next.js Route Handlersで303 See Otherのリダイレクトを実装してみる

2023/12/14に公開

この記事は、Money Forward Engineering Advent Calendar 2023 14日目の投稿です。
前日は @tosite(てっしーさん)の必見!?JaSST'23 Kyushuの舞台裏全部お見せしちゃいますでした。


こんにちは!マネーフォワードPay事業本部 プロダクト開発部 クライアントグループ フロントエンドチーム のひめのです。

今回は、Next.jsのRoute Handlersでの303 See otherでのリダイレクトを実装してみることで、App RouterとRoute Handlersについての理解を深めていこうと思います。

303 See Otherとは

303 See Otherは、サーバーがクライアントに対して、他のページへリダイレクトすることを示すレスポンスコードです。
このコードは主にフォーム送信で使用され、POSTリクエストを実行した後に、クライアントをGETリクエストによって別のページにリダイレクトさせるために使用されることがあります。

詳しくは、MDNやRFC7231をご参照ください。

https://developer.mozilla.org/ja/docs/Web/HTTP/Status/303
https://datatracker.ietf.org/doc/html/rfc7231#section-6.4.4

リダイレクトの種類

ステータスコードごとのリダイレクトの取り扱いは、仕様としてRFCに定義されています。

しかし、実際のブラウザの挙動には違うものが存在します。(301/302)
また、リダイレクトのおおまかな種類として恒久的(301/308)か一時的(302/303/307)かという違いもあります。

詳細についてはMDNがとてもわかりやすいので、リダイレクトのステータスコードに困った場合は参考にすると便利です。

https://developer.mozilla.org/ja/docs/Web/HTTP/Redirections

Route Handlersとは

次に、Next.js App Routerから導入されたRoute Handlersについてです。

Next.jsをよく利用しているユーザー向けに簡単に説明すると、Pages Routerで存在していたAPI Routesにあたるものです。

https://nextjs.org/docs/app/building-your-application/routing/route-handlers

API Routesとの違いについて、簡単に表にまとめました。

特徴 API Routes (Pages Router) Route Handlers (App Router)
ディレクトリ /pages/api /app
ファイル名 任意 route.ts / route.js
HTTPメソッドの扱い ひとつのハンドラー関数でメソッドごとにケース分け HTTPメソッドごとに関数がわかれる (GET, POST, DELETE など)
利用可能なAPI NextApiRequest NextApiResponse NextApiRequest NextApiResponse
Fetch APIの Request Response

ためしてみる

ある程度前提となる知識を確認したところで、早速簡単なフォームをつくっていきたいと思います。

実行環境

Node.jsは v20.9.0 、Next.jsは v14.0.4 を利用して進めています。
また、ブラウザはGoogle Chrome 119で確認しています。

準備

まずは、Next.jsを導入しましょう。

$ npx create-next-app --typescript .

ディレクトリ名などは適宜変更してください。今回は以下のように配置しました。

✔ What is your project named? … next-see-other
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes

後々スタイルが見づらくなるため、 global.css の色味設定を消しておきます。

/src/app/globals.css
@@ -1,27 +1,3 @@
 @tailwind base;
 @tailwind components;
 @tailwind utilities;
-
-:root {
-  --foreground-rgb: 0, 0, 0;
-  --background-start-rgb: 214, 219, 220;
-  --background-end-rgb: 255, 255, 255;
-}
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    --foreground-rgb: 255, 255, 255;
-    --background-start-rgb: 0, 0, 0;
-    --background-end-rgb: 0, 0, 0;
-  }
-}
-
-body {
-  color: rgb(var(--foreground-rgb));
-  background: linear-gradient(
-      to bottom,
-      transparent,
-      rgb(var(--background-end-rgb))
-    )
-    rgb(var(--background-start-rgb));
-}

この状態で、ローカルサーバーが立ち上がるのを確認しましょう。

$ npm run dev

フォームをつくる

/form-action/submit というエンドポイントにPOSTリクエストを送るという簡単なフォームを /src/app/form-action/page.tsx につくります。

/src/app/form-action/page.tsx
const FormActionPage = () => {
  return (
    <main>
      <form
        action={"/form-action/submit"}
        method="POST"
        className="grid gap-4 border border-slate-700 rounded m-4 p-4"
      >
        <button
          className="border border-slate-700 rounded-sm text-slate-700 p-2"
          type="submit"
        >
          Post Text
        </button>
      </form>
    </main>
  );
};

export default FormActionPage;

このようなとても簡素なページができます。

実際のページ

この状態では、「Post Text」をクリックしてもページが存在しないため、Next.jsが用意している404ページが表示されます。

POST /form-action/submit を実装する

つぎに、Route Handlersを用いて303リダイレクトを返す関数をつくります。

/src/app/form-action/submit/route.ts
import { NextApiRequest, NextApiResponse } from "next";
import { NextResponse } from "next/server";

export const POST = async (_req: NextApiRequest, _res: NextApiResponse) => {
  // 必要があれば、submit時のFormDataを取得してDBに保存する処理などを追加
  return NextResponse.redirect(
    new URL("http://localhost:3000/form-action/result"),
    {
      status: 303,
    }
  );
};

NextResponse#redirect は、2つ目の引数に ResponseInit を渡すことができるため、ステータスコードを変更することが可能です。

型は以下になります。

  static redirect(url: string | NextURL | URL, init?: number | ResponseInit): NextResponse<unknown>;

結果に表示するページを用意する

ここまででフォームのsubmitは送信されますが、リダイレクト先のページを簡単につくります。

/src/app/form-action/result/page.tsx
const ResultPage = () => {
  return (
    <div className="p-4">
      <h1>Result Page</h1>
    </div>
  );
};

export default ResultPage;

動作を確認する

実際に、フォームの動作を確認してみましょう。

「Post Text」をクリックすると、 /form-action/result へ遷移します。

結果ページ

ChromeのDevToolsで「Network」タブを開き、submitの列を確認すると、以下が確認できます。

  • ステータスコードが303 See otherになっている
  • Locationに http://localhost:3000/form-action/result が設定されている

DevTools

ここまでで、Route Handlersを用いたリダイレクトの実装を一通り実装できました。
おつかれさまでした!

さいごに

Server Actionsもそうですが、Route Handlersによっても、Next.jsで表現できる幅が広がったように感じます。
キャッシュの理解やServer Componentの役割など、まだまだ学ぶことが多くて楽しみです。

最後までお読みいただきありがとうございました。

明日はmasami moritaさんの記事になります!

Discussion