20251020

2025/10/20

ブログ

https://nextjs.org/docs/app/getting-started/route-handlers-and-middleware

https://document.microcms.io/manual/webhook-setting

microCMSのWebHookを受け取るルートを作る。

APIエンドポイントを作成するには Route Handler 機能を使う。

Route Handler は route.ts ファイル内で定義する。

route.ts 内では GETPOSTPUTPATCHDELETEHEADOPTIONS のそれぞれのHTTPメソッドごとの処理を定義できる。

microCMSのWebHookは POST で送信されるため POST のみの定義でいい。

Request の代わりにヘルパー関数などがついた NextRequest が使える。

// app/blog/api/webhook/microcms/route.ts

import type { NextRequest } from 'next/server'

export async function POST(request: NextRequest) {
  // ここに処理
  
  return Response.json({
    'message': 'sample',
  });
}

Route Handler はキャッシュされない。

ただし GET をキャッシュしたい場合がある。その場合は route.ts 内に export const dynamic = 'force-static' と記述する。この記述があったとしてもキャッシュされるのは GET のみで、その他のメソッドはキャッシュされない。

WebHookシークレットの設定

正統なWebHookリクエストかどうかを確認するために使用するシークレットキー。

microCMSの公式ページにあるように ruby -rsecurerandom -e 'puts SecureRandom.hex(20)' などで生成できる。

microCMS側とアプリ側(Vercelでうごしている場合はVercelのEnvironment Variables)に同じ値で設定が必要。

microCMSからシークレットキーと request.body を元にしたシグネチャが x-microcms-signature ヘッダーで渡されるため、アプリ側でも同様の手順でシグネチャを作成し、一致するか検証する。

全文

import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
import { revalidatePath } from "next/cache";

export async function POST(request: NextRequest) {
  const body = await request.text();

  // 送られてきたシグネチャの取得
  const signature = request.headers.get('x-microcms-signature');
  if (!signature) {
    return NextResponse.json({error: 'Invalid signature.'}, {status: 401});
  }

  // シグネチャを導出するためのシークレットキーを環境変数から取得
  const secret = process.env.MICROCMS_WEBHOOK_SECRET;
  if (!secret) {
    return NextResponse.json({error: 'Server error'}, {status: 500});
  }

  // シークレットキーとリクエストボディからシグネチャを計算
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  // 比較用にBufferオブジェクトに変換
  const sigBuf = Buffer.from(signature, 'hex');
  const expBuf = Buffer.from(expectedSignature, 'hex');

  // 送られてきたシグネチャとこちらで計算したシグネチャを比較
  if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
    return NextResponse.json({error: 'Invalid signature.'}, {status: 401});
  }

  // キャッシュの破棄
  revalidatePath('/blog');
  
  return NextResponse.json({ok: true}, {status: 200});
}
  • リクエストに x-microcms-signature ヘッダーがない場合 → 401
  • MICROCMS_WEBHOOK_SECRET 環境変数が取得できない場合 → 500
  • 受け取ったシグネチャとこちらで生成したシグネチャが一致しない場合 → 401
  • シグネチャが一致した場合 → 記事一覧のキャッシュを破棄して200

デバッグ

Vercelを使っているなら console.log で出力したものが Vercel のログ画面で確認できる。

(いまいち見方がわからない)

Next.js

dynamic

https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config

ルートセグメント設定の1つ。

設定可能な値は 'auto' 'force-dynamic' 'error' 'force-static’ 。デフォルトは 'auto'

  • 'auto'
    • 動的APIを許容しつつ、可能な限りキャッシュする。
    • 動的レンダリングとして扱われるか静的レンダリングとして扱われるかは不明。
      • おそらく基本的に静的レンダリングになり必要であれば動的レンダリングになる。
        • どういう時に動的レンダリングになるか不明。
  • 'force-dynamic'
    • 動的レンダリング(dynamic rendering)を強制する。
    • リクエストに応じてレンダリングされる。
  • 'error'
    • 静的レンダリング(static rendering)を強制する。
    • キャッシュされる。
    • 動的APIまたはキャッシュされていないデータを使用している場合はエラーが発生する。
  • 'force-static'
    • 静的レンダリング(static rendering)を強制する。
    • キャッシュされる。
    • cookiesheaders()useSearchParams() は空の値が返される。

リンク先が静的レンダリングの場合、プリフェッチが有効になる。