Open9

Vercel でホスティングしてる Next.js に Cloudflare の Cache を適用する

ピン留めされたアイテム
naporitannaporitan

現状 Cloudflare ダッシュボードの設定でどうにかなるわけではないので worker を経由する必要があるぽい。

Cloudflare Worker にこれを設定して

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    const varyKeys = [
      'RSC', 
      'Next-Router-State-Tree', 
      'Next-Router-Prefetch', 
      'Accept-Encoding',
    ]

    // append these headers to the searchParams in order to distinguish requests with them or not
    for(const key of varyKeys) {
      if (request.headers.has(key) ) {
        url.searchParams.set(key, request.headers.get(key))
      }
    }

    const rewrittenRequest = new Request(url, request);
    let cache = true

    // Do not cache routes starting with `/api`
    if(url.pathname.startsWith('/api')) {
      cache = false;
    }

    let response = await fetch(rewrittenRequest, {
      cf: {
        cacheEverything: cache,
        // only cache 2xx status codes
        cacheTtlByStatus: { 404: 1, "500-599": 0, "300-399": 1 }
      },
    });
    // Reconstruct the Response object to make its headers mutable.
    response = new Response(response.body, response);
    return response;
  },
};

Next.js の middleware にこれを設定するといいらしい

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

export function middleware(request: NextRequest) {
    const url = new URL(request.nextUrl);

    // Check for vary headers appended by cloudfare worker
    const varyKeys = [
        'RSC',
        'Next-Router-State-Tree',
        'Next-Router-Prefetch',
        'Accept-Encoding',
    ];

    let varyFound = false;

    // remove these headers from the URL
    for (const key of varyKeys) {
        if (url.searchParams.has(key)) {
            url.searchParams.delete(key);
            varyFound = true;
        }
    }

    if (varyFound) {
        return NextResponse.rewrite(url);
    }
    return NextResponse.next();
}

export const config = {
    // exclude api, images & static paths (_next/static & _next/image)
    matcher: [
        '/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)',
    ],
};
naporitannaporitan

Cloudflare でドメインをとって proxied にしていれば cache されると思っていた.....

よくよく見てみると cf-cache-status: DYNAMIC になっていたため Cache を効くようにに変更してみる。

naporitannaporitan

Cache Rules

curl <URL> -I で確かめる

last-modified: Sun, 03 Mar 2024 15:24:17 GMT
cf-cache-status: HIT
age: 73

できてる

naporitannaporitan

Link コンポーネントで遷移する部分の挙動がおかしい。
Link で SPA 的な挙動をするときは問題ないが、target="_blank" で開くと RSC がそのまま表示される。

RSC

naporitannaporitan

とりあえず Vary header をみるように Cloudfalre Cache Rules を設定できるか調べる。

https://github.com/vercel/next.js/issues/48569#issuecomment-1518887170

Ok, after an intense testing, i found out that cloudfare CDN does not respect the Vary header to properly cache different responses types.
to quote from Cloudfare docs :

vary — Cloudflare does not consider vary values in caching decisions. Nevertheless, vary values are respected when Vary for images is configured and when the vary header is vary: accept-encoding.

むりだったw

naporitannaporitan

これをやってもさらに microcms からの更新で cache purge の設定も必要なのでサクッとできなさそうなのでいったんここで調べるのはやめる