Next.jsのpagesルーターでは、APIルートが準備されている。pagesフォルダの配下にapiというフォルダを作り、その中にAPIを作ることができた。
Appルーターでは少し作り方が変わっている。
以下はAppルーターのルートハンドラーについての公式ページ。
Appルーターでの設定方法と、簡単なテスト用のAPIをメモしておく。
あくまでテスト用なので、本番で使うことは想定していない。
Next.js Appルーターのroute.ts
作り方はすごく簡単で、フォルダ内にroute.tsを作るだけ。
例えばtest-apiフォルダにroute.tsを作ると、.com/test-apiにアクセスすることでAPIとして使える。
以下はとても簡単なテスト用のAPI。
アクセスすると、ランダムな数字をjson形式で返す。
//test-api/route.ts
import { NextResponse } from "next/server";
export async function GET() {
console.log(">>> Called /test-api ");
return NextResponse.json({ data: Math.random() });
}
上の例のMath.random()のところに、返したいjsonの内容をべた書き(変数でもよい)すれば、いろんなデータを返すことができる。
応用としてはtsファイルをインポートして、json形式で返したり、ローカルファイルを開いてデータを返したり、とかができる。
以下にローカルファイルを開く場合の例を記載する。
本番環境ではそんなことはしないだろうが、ローカルテストを想定し、テスト用のjsonファイルを準備しておいてそれを呼び出す形で作る。
開発中のサイトからfetchを使って、ローカルホストでnext devしているAPIを呼べばテストできる。
jsonファイルを開いて返すAPI
テスト用のデータを返すAPI。
今回はjsonを使っているが、少し修正すればcsvなど他のファイルでも対応できる。
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";
export async function GET() {
const postsDirectory = path.join(process.cwd(), "test");
const fullPath = path.join(postsDirectory, `test-data.json`);
const fileContents = fs.readFileSync(fullPath, "utf-8");
const data = JSON.parse(fileContents);
console.log(`>>> Called test-data api);
return NextResponse.json(data);
}
node.jsのファイル読み込みを使ってjsonファイルを読み込み、それをjson形式にして返すだけのAPI。
まずファイルがある場所のパスを取得するため、実行時のカレントディレクトリをprocess.cwd()で取得し、その配下のtestフォルダを指定するためpathを連結している。
postsDirectoryで作ったパスにファイル名をくっつけて、readFileSyncでファイルを読み込む。
fs.readFileSyncは、同期処理になる。要はファイルを開き終わってから後続の処理をスタートするということ。
あとは開いたファイルをjson形式にして、それを返すだけ。
多分本番のサーバーでこんな使い方はしないだろうけど、ローカルでテスト用に使うにはとても便利。
次はDynamic Routesを使ったAPIの例。
Dynamic Routesを使ってjsonを返す
やっていることは変わらないが、まず[]で囲ったフォルダを作り、名前を[id]としておく。
例えばtest-dynamic/aでアクセスがあった場合、idはaになる。
このaを取得してあれこれするのがDynamic Routesを使ったAPI。
idに対応するファイル名のjsonを用意しておいて、ファイル名を生成するときに、取得したidを使う。
ちなみに存在しないidの場合、ステータスコードは500、「このページは動作していません」とテキストが出る。本番で使うことは想定していないので、対応する処理は入れてない。
// test-dynamic/[id]/route.ts
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";
export async function GET(
request: Request,
{ params }: { params: { id: string } },
) {{
const postsDirectory = path.join(process.cwd(), "test");
const id = params.id;
const fullPath = path.join(postsDirectory, `${id}.json`);
const fileContents = fs.readFileSync(fullPath, "utf-8");
const data = JSON.parse(fileContents);
console.log(`>>> Called test-data api ${id}`);
return NextResponse.json(data);
}
開発環境とvercel本番環境の違い
next devで動く開発サーバーは、静的で作っていてもSSR的に動く。
そのため、vercelなど本番環境に上げる場合、ビルドした時にデータはキャッシュされる。
例えば、該当のurlにアクセスしたときの時間を返すAPIを静的にビルドしてvercelに上げると、ビルドしたときの時間を永遠に返すAPIになる。
そういう理由から、SSR的な挙動をさせたいAPIをvercelに上げてテストに使いたい、なんて時は注意が必要になる。
これの対処方法は、ファイルの中に、キャッシュしないでねと自分で記述すること。
Next.jsでは明示的に示さない限り、ビルドすると静的なサイトというか、データは全てキャッシュされる。
キャッシュしたくないところは、自身で明示する必要がある。
それが以下のexport const dynamic = "force-dynamic";のところ。
import ja from "date-fns/locale/ja";
import { format } from "date-fns";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET() {
const now = new Date(
Date.now() + (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000,
);
const nowJa = format(now, "yyyy年M月d日(E) HH:mm:ss", { locale: ja });
console.log(">>> Called api アクセスした日付を返すAPI");
console.log(nowJa);
return NextResponse.json({ data: nowJa });
}
dynamicの値を変えることで、キャッシュの動きを制御できる。
何も指定しない場合は基本的には全てキャッシュされる(一部例外あり)。
上の例のように、force-dynamicにすると、このAPIはキャッシュされずに、アクセスがあると再検証が走る。pageルーターのgetServerSidePropsと同じ動きになる。
詳細は以下で。
next devで使う分には問題ないし、普通はこれをvercelに上げるなんてことはないと思うけど。
vercel自体の動きをテスト確認したいとか、特殊な状況でテスト用のAPIを本番でも使いたいという時に、キャッシュの指定をしないと動かないよ、というだけの話。
まとめ
本番のAPIがまだできてないとか、APIへのアクセスが従量課金とか、様々な理由でAPIに接続できないけど、データ取得の部分は作っておきたいし、本番を想定したデータでデザイン確認したい、なんて時にNext.jsのAPI機能はとても便利。
Appルーターで新しくなったfetchでローカルのテストAPIにつなげておいて、本番APIが使える段階になったら、接続先を変えてテストをすればよい、というのは楽。
pageルーターの時のようにフォルダ名の縛りがないので、そこかしこに適当にroute.tsを作ってしまって管理が大変になりそうだなとは思う。

