mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
fix(deploy): Cache-Control headers so CDN stops serving stale HTML
Root cause: Hostinger's CDN was applying s-maxage=31536000 (1 year)
to prerendered HTML pages by default. Each deploy produced fresh
Turbopack chunk hashes, but the edge kept serving year-old HTML that
referenced chunk filenames the new build no longer had on disk —
resulting in HTML 200 + every /_next/static/*.{js,css} 404, page
rendered unstyled. Private/incognito didn't help because the cache
is at the edge, not the browser.
Fix: explicit Cache-Control via next.config.ts headers():
- /_next/static/* — public, max-age=31536000, immutable
Content-hashed filenames; safe forever.
- /api/* — no-store
Per-user, must never be shared at the edge.
- everything else — public, max-age=0, s-maxage=300,
stale-while-revalidate=86400
Edge serves instantly from cache for 5 min, then keeps serving
cached content while refreshing in the background for up to 24h.
Chunk-hash drift after a deploy self-heals within ~5 min.
Verification after deploy:
curl -I https://<host>/login | grep -i cache-control
Expect: public, max-age=0, s-maxage=300, stale-while-revalidate=86400
(If Hostinger overwrites this, fall back to configuring
purge-on-deploy at the platform layer.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ed8dd6de84
commit
56c76af739
@ -1,7 +1,65 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
/**
|
||||
* Cache-Control policy.
|
||||
*
|
||||
* Why this exists:
|
||||
* Hostinger's CDN was applying `s-maxage=31536000` (1 year) to
|
||||
* prerendered HTML pages by default. When a new deploy shipped
|
||||
* fresh Turbopack chunk hashes, the edge kept serving year-old
|
||||
* HTML referencing chunk filenames that no longer existed on
|
||||
* disk — result: HTML 200, every /_next/static/*.js and .css
|
||||
* came back 404, the page rendered unstyled. Private/incognito
|
||||
* did nothing because the cache is server-side.
|
||||
*
|
||||
* Strategy:
|
||||
* - /_next/static/* — immutable for a year. Filenames are
|
||||
* content-hashed, so a new build produces new filenames; the
|
||||
* old ones are safe to keep indefinitely in caches.
|
||||
* - /api/* — no-store. API responses are per-user and
|
||||
* must never be shared across requests at the edge.
|
||||
* - Everything else — public, brief s-maxage + generous
|
||||
* stale-while-revalidate. The edge serves instantly from cache
|
||||
* for the first 5 min, then returns cached content while
|
||||
* refreshing in the background for up to 24 h. A deploy's
|
||||
* chunk-hash drift self-heals within ~5 min with no user-
|
||||
* visible latency.
|
||||
*
|
||||
* Note: dynamic dashboard routes (/inbox, /contacts, /pipelines,
|
||||
* /broadcasts, etc.) are server-rendered per request — Next.js
|
||||
* and Supabase auth already prevent them from being served
|
||||
* from a shared cache. The s-maxage here is a ceiling; Next.js
|
||||
* and auth middleware still set `private` / `no-store` for
|
||||
* per-user responses.
|
||||
*/
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/_next/static/:path*",
|
||||
headers: [
|
||||
{
|
||||
key: "Cache-Control",
|
||||
value: "public, max-age=31536000, immutable",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: "/api/:path*",
|
||||
headers: [{ key: "Cache-Control", value: "no-store" }],
|
||||
},
|
||||
{
|
||||
source: "/:path*",
|
||||
headers: [
|
||||
{
|
||||
key: "Cache-Control",
|
||||
value:
|
||||
"public, max-age=0, s-maxage=300, stale-while-revalidate=86400",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user