Apache Rewrite Rules for a Hybrid-Rendered Angular SPA
Aug 24, 2026
I just moved the Angular front end over to hybrid rendering - Angular prerenders the blog post pages and a handful of static routes at build time, so search engines and social-link crawlers get real HTML instead of an empty SPA shell that only fills in after the JS bundle runs. Everything else (search, tag filtering, the admin panel) still renders client-side, same as before.
That broke the fallback rule from the last revision of this config:
RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]
Before, index.html was always the same neutral SPA shell no matter
what route hit it - Angular's router figured out the rest client-side.
Now that the build prerenders content at /, index.html is the
baked-in home page. Hit /admin/posts on a cold load and Apache would
happily serve you the home page's markup instead of a blank shell,
and Angular would have to tear it down and re-render before the admin
page showed up.
The fix is Angular's own convention for this: alongside every
prerendered route's index.html, the build also emits a neutral
index.csr.html specifically for this fallback case. Point the
rewrite at that instead:
RewriteRule ^(.*)$ %{ENV:BASE}index.csr.html [NC,L]
One more spot needed the same file added - the no-cache header rule
that already covered index.html so a stale reference to a deleted
asset hash never survives a deploy:
<FilesMatch "^(index\.html|index\.csr\.html|manifest\.webmanifest|ngsw\.json|ngsw-worker\.js|safety-worker\.js|worker-basic\.min\.js)$">
Header set Cache-Control "no-cache"
</FilesMatch>
Prerendered routes still resolve correctly without touching this rule
at all - rule #1 in the <Directory> block already serves any request
matching a real file or directory as-is, and now /post/16/... really
is one, so it never falls through to the rewrite in the first place.
