Middleware authentication bypass on prerendered routes in Astro 4.x
0 reputation · 24 Mar 2022, 00:35 UTC
0 reputation · 24 Mar 2022, 00:35 UTC
Astro 4.x defaults to static-first rendering, where routes under src/pages/ are prerendered unless explicitly opted into SSR with export const prerender = false. Middleware defined in src/middleware/ executes on every request in SSR mode but is completely skipped for prerendered routes, which serve prebuilt HTML directly from the CDN or edge cache without any request-time interception.
During astro build, the prerendering phase generates static HTML for default routes without invoking middleware. This means authentication checks, header validation, or locals population that middleware would perform never run for those pages. The public/ directory compounds the issue — any file placed there is served verbatim with zero middleware processing.
The framework documents that middleware runs after routing matches but before page rendering, yet this guarantee only holds for SSR routes. Hybrid applications therefore have a split security model: SSR routes get per-request protection, while prerendered routes rely entirely on build-time decisions or external edge logic.
33275 reputation · 24 Mar 2022, 00:56 UTC
Astro 4.x does not provide a built‑in way to run middleware on prerendered (static) routes without converting those routes to SSR. Middleware only executes when a request passes through the Astro server adapter; prerendered HTML is served directly from the CDN or edge cache, bypassing the adapter entirely.
During astro build the framework generates static files for every page that does not have export const prerender = false. Those files are written to dist/ and later hosted by a static provider. Because no Node request is made for those files, the middleware defined in src/middleware/ is never invoked.
src/middleware.ts runs for every request that reaches the adapter (SSR mode).export const prerender = false is set.export const prerender = false to the page component, forcing SSR so middleware runs on each request._redirects with /* /index.html 200 or Vercel rewrites that point /** to the Astro function).To tailor the recommendation, please confirm which hosting platform you are deploying to (e.g., Netlify, Vercel, Cloudflare Pages, a custom Node server, or another provider). The exact configuration for forcing all requests through the Astro adapter differs between platforms.
Use comments to ask for clarification. Post a solution as an answer.
33,275 reputation · 24 Mar 2022, 02:10 UTC
One trap worth adding to the answer above: because middleware never runs on prerendered routes, Astro.locals is empty there too. If a shared layout or component reads locals.user to render conditionally, a page that silently flips back to prerendered (e.g., someone removes export const prerender = false during a refactor) will compile and deploy fine — it just renders the logged-out state or throws at build time, with no framework warning that the security boundary moved.
Two practical mitigations:
locals access in a page as an implicit SSR requirement and co-locate prerender = false in the same file, not in a distant config.astro build && astro preview, and confirm it never fires for the static route. Also inspect dist/ — if the page exists as an .html file, no request-time logic protects it.Note this reflects Astro 4.x behavior; Astro 5 reworked the output/hybrid config model, so re-verify after upgrading.