Next.js 13のRoute Handlersに移行したぞ!
はじめに
Next.js 13でApp Routerが紹介され、ReactのServer Componentsを使ったサーバサイド・レンダリングが大きな話題となりました。
そんなApp Routerの機能の中に、バージョン13.2のアップデートでRoute Handlersなる機能がしれっと追加されました。従来のPages RouterのAPI Routesに代わる、Next.jsアプリのWebサーバ側の処理を担う重要な機能です。しかし、Server Componentsが目玉機能ということもあってか、公式サイトの説明も簡素なもので、ネット上の情報も、少し少ないように感じます。
そんなこともあって、このサイトも2023年7月からApp Routerに移行したものの、Route Handlersは利用せず、Webサーバ側の処理は従来のAPI Routesを使っていました。
今回、意を決してほぼ全てのAPI RoutesをRoute Handlersに移行しましたので、主な使い方や注意ポイントを共有したいと思います。
この記事の説明範囲
内容
Next.js 13.2で導入されたApp Routerの機能の一部である、Route Handlersに関する説明となります。
page.jsxやlayout.jsxのように、Webページの生成に関する機能はNext.js13のApp Routerを試してみたぞ!で記事にしていますので、こちらをご覧ください。
また、Server Actionsなる新機能も導入されていますが、こちらはまだアルファ(実験中)の機能とされていますし、私もまだ試せていないので対象外となります。
想定読者
私のように、「App Routerを導入したけど、Route Handlersに移行できていない」方を主に想定しています。しかし、「Next.js 13へのバージョンアップを検討している」方や、「別のフレームワークからNext.jsへの移行を検討している方」でも、「Next.jsではこんなふうにWebサーバ側の処理を書くのだな~」と参考にしていただけるかなと思います。
環境
Route HandlersはNext.jsのバージョン13.2で導入されていますので、Next.js 13.2以降である必要があります。
ちなみに、私のバージョンは13.4.1
です。
また、例示は全てTypeScriptで記述しています。
用語について
基本は公式ドキュメントに準拠します。
Pages Router(v12以前)
Next.js v12までのルーティング機能です。/pages
フォルダ直下に配置したファイル単位でページを生成してくれる機能ですね。
API Routes(v12以前)
Pages Routerで、/pages/api
フォルダに配置したjsファイルがAPIのエンドポイントとして機能する機能です。Webサーバとしての役割を担っています。
App Router(v13以降)
Next.js v13で導入された、従来のPages Routerに代わる新ルーティング機能です。/app
フォルダに配置した、page.jsx
がページとして生成されます。
Route Handlers (v13.2以降)
App Router版のWebサーバ機能です。従来のAPI Routesに代わる機能となります。
その他
-
このページでは、「エンドポイント」は
https://www.test.com/api/comments
、https://www.test.com/api/login
のように、ディレクトリ名を含んだURLのことを指しています。 -
JavaScriptの実行環境はブラウザとNode.jsの2種類あります。このページで「Web版のJavaScript」とか「ブラウザ版のJavaScript」と記載してある場合、「ブラウザで実行されるJavaScript」のことを指しています。
Route Handlersの基礎
まずは、使い方のルールや基礎を見ていきます。英語ですが以下の公式ドキュメントに詳しく書いてあるので、参考にしてください。
ファイル配置やネーミング
App Routerではpage.jsx
、layout.jsx
のようなネーミング・ルール(File Convention)が導入されました。
Route HandlersもApp Routerの機能の一つなので、このルールに沿ってファイル名を設定してあげる必要があります。
ルールは簡単で、以下の通りです。
/app
フォルダ内に配置- ファイル名は
route.js
- page.jsxと同じ階層に置いちゃダメ
例えば、クライアント側からfetch("/api/admin")
と呼び出す場合、route.jsファイルは/app/api/admin/route.js
に配置されている必要があります。
Pages RouterのAPI Routesでは、/pages
直下にapiフォルダを作成し、その中に置かれたjsファイルが、ファイル名込みでエンドポイントとして機能していました(上記の例では、/pages/api/admin.js
のようにファイル配置が必要)。
Route Handlersでは、route.jsとファイル名が固定になりましたが、/app
フォルダ傘下であれば任意の場所に配置が出来るようになりました。apiフォルダを作ることは必須ではありません。
ただし、/app/admin
のようなパスを与えたときに、ページ(/app/admin/page.jsx
)とWebサーバのエンドポイント(/app/admin/routes.js
)の両方が存在していると、どちらを実行すれば良いのか、Next.jsは分からなくなってしまいます。そのため、pages.jsxとroute.jsは同じ階層に配置出来ません。
例えば、下のようなフォルダ構成があったとします。「/test」ページとWebサーバのエンドポイントの「/test」が存在しているので、エラーとなります。
app
│ layout.tsx
│ page.tsx
│
└─test
page.tsx
route.ts
上記の状態で開発環境を実行すると、ページをブラウザで開く際に以下のようなエラーメッセージが出ました。分かりにくいですね。
Server Error TypeError: entries is not iterable
なので、好きな場所に配置できるようになったものの、私は/app/api
フォルダを作成してそこに入れています。
文法
関数名のルール
route.jsでは、受け付けるhttpのメソッド(GET、POST等)に応じた関数を定義します。GETに応じた処理なら、関数名はGET、POSTに応じた処理なら関数名はPOSTとするのがルールです。
例えば、/app/api/test
にGETメソッドを送ると、JSON形式で{"msg":"hello,wolrd!"}
を返す処理があったとします。
この場合、route.jsは以下のように記述します。なお、ファイルはルールに沿って/app/api/test
に配置する必要があります。
import { NextRequest, NextResponse } from "next/server";
export function GET(req: NextRequest) {
const res = NextResponse.json({ msg: "hello,world!" })
return res;
}
今回はGETメソッドに対応しているため、関数名はGETです。メソッドに応じてPOST,DELETE,HEAD,PUT,PATCHも関数名に出来ますが、いずれの場合も関数の引数は全てNextRequest型オブジェクトで同じです。
exportについて
default exportはダメです。export default
を付けると、以下のようなエラーで「named exportにしてくれ」と怒られます。
Detected default export in 'C:\Users\app\api\test\route.ts'. Export a named export for each HTTP method instead.
これは、GETとPOST等、複数のメソッドに応じた処理を同じroute.jsに定義することが出来るためかと思います。defaultは付けずにexportしましょう。
NextRequestとNextResponseについて
Next.js v12までのAPI Routesでは、リクエストはNextApiRequest型、レスポンスはNextAPiResponse型でしたが、それぞれNextRequestとNextResponseと型が変わっています。
もともとブラウザのJavaScriptで実装されていた、Request型とResponse型を、それぞれ拡張したオブジェクトとして実装されています。ブラウザとほぼ同じ書き方ができるので便利ですね。
詳細は後述しますが、NextRequestとNextResponseのドキュメントは以下のとおりですので参考にしてください。
API Routesとの違い
ここで、従来のAPI Routesとの違いをまとめたいと思います。
参考までに、以前のAPI Routesで同じ処理を書くと以下のようになります。
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
// GET以外はエラー
if (req.method !== "GET") {
res.status(405).json({ msg: "wrong method" });
}
res.json({ msg: "hello,world!" });
}
-
API Routesでは、default exportが必要。Route Handlersでは逆にdefault exportしてはダメ。
-
API Routesでは、httpのメソッドを問わず関数が実行されるため、関数内で制御が必要。Route Handlersでは、httpのメソッド名を関数名にする必要があり、対応した関数が呼び出される。
-
API Routesでは、第一引数にNextApiRequest、第二引数にNextApiResponseを受け取る。Route Handlersでは、第一引数にNextRequestのみ受け取り、レスポンスは「next/server」からimportしたNextResponseを直接使う。
-
API Routesでは、NextApiResponseの「.json()、.end()、.send()」等のメソッドを実行することで、クライアントにレスポンスを返す。Route Handlersでは、NextResponse型のオブジェクトを明示的にreturnすることで、レスポンスを返す。例示のNextResponse.json()も、NextResponse型のオブジェクトを返している。
-
Route Handlersでは、NextResponse型のオブジェクトをreturnしなかった場合、クライアントにはhttpステータス:500(Internal Server Error)が返される。
後、リクエストのボディ部の扱い方が結構変わっています。API Routesでは、jsonやtextのボディは勝手にparseしてくれましたが、ファイルのアップロードを含むmultipart/form-dataには対応していませんでした。これを扱うためには、サード・パーティ製のparserをインストールしたり、configでデフォルトのparserを無効にしたり、、、結構煩雑でした。
Route Handlersではかなりシンプルに対応できるようになっています。私も、multerを使っていましたが、移行して不要になりました。
詳しくは、後半の実例部分で紹介します。
メリット
ドキュメントを見る限り、ウリは「cache機能」かと思います。
Route Handlersでは、条件を満たす場合はレスポンスをキャッシュし、処理を高速化してくれます。App Routerのfetchを使ったレンダリングでもキャッシュしてくれますが、概念的には同じです。
ただ、今のところキャッシュが有効になるのは「GETメソッドでリクエストにアクセスしない場合」のみと限定的ではあります。リクエストのヘッダーやcookieにアクセスしないなら、どのリクエストでもレスポンスの値は変わらないので、毎度計算せずにキャッシュして返してしまおう、ということですかね。(Dateで現在時刻を返す時もキャッシュしてしまうのだろうか、、、)
GET以外のメソッドや、GETでもcookieやヘッダを扱う場合、キャッシュは無効になります。
また、キャッシュ機能は手動でオフにしたり、キャッシュの更新間隔(revalidate)を設定することができます。
詳しくは、Route Segment Configに記載されています。
キャッシュを無効にする場合は、route.jsファイルにexport const dynamic = 'force-dynamic'
、更新間隔を設定する場合はexport const revalidate = 60
のように秒数を指定して書いてあげればよさそうですね。
他にも、リクエストやレスポンスがブラウザのJavaScriptで実装されていたRequest型やResponse型を拡張しているので、全体的に使いやすくなったと言えると思います。
気になる点
JavaScriptには御存知のとおり、ブラウザとNode.jsと2つのランタイムがあります。ブラウザのほうで既に実装されていたRequestやResponseや、fetchやFormDataといった関数や型を、Node.jsでも実装するように取り組みが行われています。
Route Handlersでも、これらのNode.jsの新機能がふんだんに使われています。ここで気になるのは、現在のNode.jsのLTS(v18)では、まだこれらの機能は「Stability: 1 - Experimental.」と記載がされていることです。
まだ実験途中のステータスのように見えますが、、、これをベースにフレームワークを作ってしまっても良いのか?と少し気になっています。ブラウザ版JavaScriptで安定稼働している機能なので、最終的にはそこに合う形で実装されると踏んでいるのでしょうか?
とは言え、Nodeのexperimentalがstableになるまでの流れなどをちゃんと理解できていないので、あくまでも私が個人的に気になっているだけ、ということでお願いいたします。。。
Pages Routerとの併用について
API RouterとRoutes Handlersは併用可能です。なので順次移行して問題ないと思います。このサイトも、まずはページのレンダリングは先にApp Routerに移行し、WebのAPIは徐々にRoutes Handlersに移行してきました。
公式ドキュメントでは出来るとも出来ないとも書いていませんが、、、まぁ出来ないと書いていないので、出来ると解釈して良いと思います。
ただ、当然ですが、API RouterとRoutes Handlersで同じエンドポイントが存在する場合、エラーになります。
例えば、/pages/api/test.js
と/app/api/test/route.js
はいずれも/api/test
のエンドポイントとして解釈されます。
この状態で開発環境を動かすと、「pagesとappで重複しているよ」と警告してくれます。
Duplicate page detected. pages\api\test.ts and app\api\test\route.ts resolve to /api/test
実際にAPIを投げてみると、エラーとなります。
Error: Conflicting app and page file found: "app\api\test\route.ts" and "pages\api\test.ts". Please remove one to continue.
実装例
NextRequestやNextResponseの使い方を、このサイトでの実装例を交えながら見ていきたいと思います。
- パターン1:text/plainのレスポンスを返す
- パターン2:JSONのリクエストを受け取り、cookieを設定してJSONのレスポンスを返す
- パターン3: Form(ファイル含むmultipart/form-data)のリクエストを受け取り、zipファイルのレスポンスを返す
パターン1
これまでの例でも、NextResponse.json()
とすれば、JSON(Content-Type: application/json)のレスポンスがされることはお察しかと思います。
今回のパターンは、Content-Type: text/plainの単純なテキストを返す例です。
まぁ.json()で代用可能なので使う機会はあまりないかもしれませんが、、、でも、移行の際にクライアント側が.text()でレスポンスをパースしているから、それに合わせなければいけないケースもあるかと思います。
いずれにせよ、NextResponseのクセを把握するにも良いと思うので紹介します。
このサイトでは、管理画面からビルドを実行した際にテキストのメッセージを返しています。以下のように実装しています。
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
// 自作のビルドの関数です。
const msg = await build();
// msgが設定されている場合はエラー。
if (msg.length > 0) {
return new NextResponse(msg, {
status: 500,
headers: { "Content-Type": "text/plain" }
})
}
// ビルド正常終了
return new NextResponse("build started!", {
headers: { "Content-Type": "text/plain" },
});
}
NextResponseをコンストラクタとして使う
new NextResponse()
のように、NextResponseをコンストラクタとして呼び出すのがポイントです。
第一引数にレスポンスのボディに設定するテキスト、第二引数にオプションを設定しています。
第二引数のオプションは、WebのResponseの第二引数と同じです。以下のような形のオブジェクトを指定します。
{
// httpのステータス。200とか400とか。
status: number ,
// ステータスに応じたメッセージ。
statusText: string,
// Headersオブジェクトもしくは文字列リテラル
headers: Headers,
}
textを返すので、headers:{"Content-Type":"text/plain"}
を設定しています。
戻り値について
new NextResponse()
で生成したインスタンスが戻り値になっています。
Route Handlersでは、returnしたNextResponseのインスタンスがレスポンスとして扱われます。JSONを返す場合のNextResponse.json()
も、戻り値はNextResponseのインスタンスになっています。
NextResponse.json()
やnew NextResponse()
をしても、直ちにはレスポンスは送信されません。returnしたNextResponseのインスタンスが、レスポンスとして送信される点は押さえておく必要があります。
以前のAPI Routesでは、response.json()、response.send()
のようなメソッドを実行し、クライアントにレスポンスを返していました。扱い方が少し変わっていますね。
パターン2
管理者ページへのログインで使っています。
クライアント側では、ログイン・ページでユーザ名とパスワードを入力し、JSON形式でPOSTのリクエストを投げます。サーバでJSONを取り出し、ユーザ名とパスワードをチェックします。その後、認証用のcookieを設定し、リダイレクト先(管理者ページ)へのリンクをJSON形式でクライアントに返す、というのが大まかな概要です。
クライアント側
以下のようにリクエストを投げています。レスポンスを処理するところは脱線するので割愛します。
const user = "XXXX";
const password = "XXXX-YYYYY";
res = await fetch("/api/admin/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user, password }),
});
サーバ側
route.jsは以下の通りです。認証関数は自作なので、importする部分や、その他チェック処理等は一部省いています。
import { NextRequest, NextResponse } from "next/server";
interface Identifier {
user: string;
password: string;
}
export async function POST(req: NextRequest) {
const identifier = await req.json() as Identifier
const { user, password } = identifier;
// 自作の認証関数です。userとpasswordをチェック。
// OKならcookieにセットする認証キー`session`を生成します。
const { ok, session } = await authenticate(user, password);
// ユーザ名やパスワードが違う場合はステータス401を返します。
if (!ok) {
return NextResponse.json({error:"Unauthorized"}, { status: 401 })
}
// 認証OKなら、リダイレクト先をセットしたレスポンスを生成。
const res = NextResponse.json({ url: "/admin" }, { status: 200 });
// レスポンスにcookieをセットし、返します。
res.cookies.set("user", session);
return res;
}
ポイントは2つあると思っています。
Requestのボディーの抽出(パース)
1つ目は、JSON形式のリクエストのボディ部を、await req.json()
で抽出しているところです。クライアント側でAPIのレスポンスを処理するときに良く使うので、馴染みのある方は多いと思います。
NextRequestは、WebのJavaScriptのRequest型を拡張しているため、同じように使うことが出来ます。クライアントと同じようにパースできるのは便利ですね!
以前のNextApiRequestでは、JSONは勝手にパースしてくれましたが、Content-Typeによってパースがされず、configでデフォルトのパーサを無効化する必要がありました。結構分かりにくいと感じていたので、個人的には嬉しいです。
cookieの設定
2つ目は、NextResponse.json()でレスポンスを生成した後に、cookieをセットしているところです。
cookieは、レスポンスのcookies.setを使って設定しています。ここで注意なのは、cookiesはNextResponseのインスタンスからのみアクセス可能ということです。NextResponse.cookiesのように書いても、「そんなプロパティない」と怒られてしまいます。
前述のとおり、NextResponse.json()はNextResponseのインスタンスを返すので、そのインスタンスからcookiesにアクセスしています。
パターン3
クライアント側のformから、入力値や選択したファイルをサーバに送り、サーバ側では受け取った値によってファイルを加工し、zip化してクライントに返す例です。
このサイトのスマホ画像切取君で使っています。画面で入力した値と選択した画像を、サーバ側で切り取ってzip化し、クライアントに送り返しています。
クライアント側
以下のようにFormのデータを送信しています。Reactのフックを使って入力値やファイル等を取得している部分は冗長になるので落としています。変数に何が設定されているかは冒頭のコメントを確認ください。
/**
* 変数の説明
* files: FileList型。画面で選択したファイルが設定されている想定
* ratio: string。画面入力値。
* resize: string。画面入力値。
* width: string。画面有力値
*/
// FormDataを初期化
const formData = new FormData();
// fileは複数選択可能。フィールド名は`pics`とし、各ファイルをFormDataに追加。
Array.from(files).map(file => formData.append("pics", file, file.name));
// ファイル以外のデータをappend.
formData.append("ratio", ratio);
formData.append("resize", resize);
formData.append("width", customWidth);
// サーバにpost
fetch("/test/crop", {
method: "POST",
body: formData,
})
.then(res => {
// サーバからはzipファイルが帰ってくるのでblobとして扱う。
if (!res.ok) throw res;
return res.blob();
})
.then(blob => download(blob)) // downloadは自作関数。
自分でFormDataを生成し、入力値やファイルを追加しています。htmlのformタグのデフォルトのsubmit機能でも問題ないかと思います。ちなみに、ファイルは複数選択可能です。
リクエストのContent-Typeはmultipart/form-dataとなります。fetch関数では、multipart/form-dataの場合Content-Typeを明示してはいけない決まりなので、記載はありませんが。
サーバ側
import { NextRequest, NextResponse } from "next/server";
import { readFile } from "fs/promises";
export async function POST(req: NextRequest) {
// bodyからform-dataを取得
const formData = await req.formData();
const ratio = formData.get("ratio");
const resize = formData.get("resize");
const width = formData.get("width");
const pics = formData.getAll("pics") as File[];
// 画像を切り取り、zipファイル化する自作関数。
// zipファイルのパスを返す。
const zipFilePath = await myZip(pics, ratio, resize, width);
try {
const buffer = await readFile(zipFilePath);
return new NextResponse(buffer, {
status: 200,
headers: {
"Content-Type": "application/zip",
"Content-Disposition": "attachment; filename=zen-crop.zip"
}
});
} catch (e) {
// 省略:エラー時のレスオンスを返す処理。
}
}
ボディ部の抽出(パース)
パターン2では、req.json()
でリクエストのJSONのボディ部を抽出しました。今回はFormDataなのでreq.formData()
で抽出しています。これも、WebのJavaScriptのRequestを拡張しているから利用できるメソッドです。便利、、、!
前バージョンのAPI Routesでは、同じ処理をするにはサードパーティのライブラリを利用する必要がありました。
後は、抽出したformDataから、get(フィールド名)
で値を取得しています。ファイルは複数選択可能なので、getAll(フィールド名)
で取得しています。
レスポンスの生成
以下の部分です。
const buffer = await readFile(zipFilePath);
return new NextResponse(buffer, {
status: 200,
headers: {
"Content-Type": "application/zip",
"Content-Disposition": "attachment; filename=zen-crop.zip"
}
});
zipファイル形式なので、new NextResponse()
の第一引数に、zipファイルをfsパッケージのreadFileで読み取ったBuffer型データを渡しています。
第二引数にはヘッダ情報を追加しています。Content-Typeはapplication/zipとなります。後、クライアント側で画面表示ではなくダウンロードさせる想定なので、Content-Dispositionにattachmentを指定し、filenameは固定でzen-crop.zipにしています。
移行でハマったところ
基本的にはスムーズに移行できたのですが、1点だけリダイレクトがうまくいかず、ハマった箇所があったので共有します。
ページのレンダリングをApp Routerに移行した際、一部のページだけPages Routerで残ってしまっていました。今回、Route Handlersへ移行する際、良い機会なのでページのレンダリングもApp Routerに移行させました。その際に発生したトラブルとなります。
ログイン画面でパスワードとユーザ名を入力し、OKなら管理画面にリダイレクトさせる機能があります。クライアント側では、以下のようなformで、デフォルトのsubmitをそのまま利用していました。サーバからリダイレクトのレスポンスを受け取れば、リダイレクトをフォローして画面遷移するのが正常動作となるはずです。
<form action="/api/route-test">
<input type="text" name="user" value=""/>
<input type="text" name="password" value=""/>
<input type="submit" value="submit" />
</form>
移行前のAPI Routesでは、以下のように記述しており、問題なくリダイレクトされていました。
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { user, password } = req.body;
/*省略:認証処理やcookieの設定*/
res.redirect("/admin");
}
Route Handlersでも、NextResponseにredirectメソッドがあります。パラメタにフルパスを渡す必要はありますが、NextApiResponseのredirectと同様に動作すると思い、以下のように書いてみたのですが、、
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const { user, password } = req.body;
/*省略:認証処理やcookieの設定*/
const res = NextResponse.redirect(new URL("/admin", req.url));
}
しかし、これだとリダイレクトしてくれません。ブラウザがグルグルまわったままになってしまいます。試しに、デフォルトのsubmitでなく、fetch関数にredirect:"follow"
のオプションをつけて実行してみました。すると、リダイレクト先のhtmlは取得でき、グルグルは止まるもののリダイレクトはしてくれません。
しょうがないので、routes.jsでは、リダイレクト先のパスをJSON形式でクライアントに返し、クライアント側でnext/navigation
のuseRouterを使ってページ遷移させることで回避しました。
クライアント
import { useRouter } from "next/navigation";
// ページ遷移に使う
const router = useRouter();
/**
* routerでNextResponse.redirectしても、ページ遷移してくれないので、urlを返す方式に変更。
* そのため、formのPOSTをブラウザ規定でなく自作関数で実装。
*/
const submit = async (e: React.FormEvent<HTMLFormElement>) => {
// デフォルトのsubmitを無効化。
e.preventDefault();
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user, password }),
});
// レスポンスからurlを取得
const { url } = await res.json();
// こうすると遷移してくれる
router.push(url);
};
サーバ
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const { user, password } = req.body;
/*省略:認証処理やcookieの設定*/
// json形式で遷移先のパスを返す。
const res = NextResponse.json({ url: "/admin" }, { status: 200 });
}
私が試した限り、API RoutesでもRoute Handlersでも、リダイレクト先が/pages
傘下のページなら、リダイレクトされます。しかし、リダイレクト先が/app
傘下のページだと、リダイレクトはされません。
ドキュメントを見ても、リダイレクトに関する変更等は見つけられませんでしたが、、、。/app
のページはサーバ・コンポーネントが導入されているので、挙動が微妙に変わっているのかもしれません。
原因特定できていませんが、「/app
のページには、NextRequest.redirectではリダイレクト出来ないケースがある」ことは頭の片隅に入れておいてよいかと思います。
最後に
Next.js 13.2で導入されたRoute Handlersの使い方について、このサイトでの実装例を交えて紹介しました。リクエストやレスポンスを扱うオブジェクトである、NextResponseやNextRequestのクセを把握すれば、結構簡単に移行できると思います。
個人的には、リクエストのボディーの抽出が簡単になったのが便利だと感じています。お陰で、multer等のサードパーティのパッケージが不要になりました!
しかし、肝心のNode.js v18では、Next.jsが内部でバリバリ使っているRequestやResponse、FormDataといった関数がまだexperimentalのステータスなのは気になりますが、、、
他にも、middlewareの追加なども出来るようになっているので、今後使う必要性が出てきたら、別途紹介したいと思います。
参考リンク
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- route.js: https://nextjs.org/docs/app/api-reference/file-conventions/route
- NextRequest: https://nextjs.org/docs/app/api-reference/functions/next-request
- NextResponse: https://nextjs.org/docs/app/api-reference/functions/next-response