Skip to content

Add static caching via backstage/laravel-static - #39

Open
markvaneijk wants to merge 2 commits into
masterfrom
static-caching
Open

Add static caching via backstage/laravel-static#39
markvaneijk wants to merge 2 commits into
masterfrom
static-caching

Conversation

@markvaneijk

@markvaneijk markvaneijk commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Implements full-page static caching with backstage/laravel-static, addressing the SEO audit's P1 performance finding: every HTML response currently bypasses the CDN (cf-cache-status: DYNAMIC) because Laravel renders each request.

What changed

  • Pinned spatie/laravel-sitemap to 8.0.0 — the newest version still on spatie/crawler ^8, so it coexists with laravel-static (8.0.1+ require crawler ^9, which conflicts). Unpin once laravel-static supports crawler v9.
  • StaticResponse middleware on all HTML routes: /, /uses, /now, /aliases, /posts, and post pages. Sitemap, feed and socials routes stay dynamic (XML content types and OAuth flows must not be cached as .html).
  • Config (config/static.php): routes driver, include_query_string: false (pages don't vary by query string; also prevents cache-stuffing via ?ref= URLs like the ones in the backlink profile), minify_html: true.
  • Invalidation: none in-app — posts are markdown files in content/posts, so model events never fire for real publishes. Clear + warm the cache as a deploy step instead (below).
  • STATIC_ENABLED=true by default (production picks it up with no env change); local .env sets false so dev stays fresh.

Verified locally

  • On-demand caching writes minified HTML to storage/app/public/{host}/GET/{path}?.html on real requests
  • Correct canonical URLs, single-encoded entities in cached output
  • Sitemap output unchanged; feed/sitemap not cached
  • With STATIC_ENABLED=false nothing is written; test suite passes

Deploy notes

  1. nginx: add a try_files rule so cached pages skip PHP entirely (note the literal ? in filenames — the package's naming convention):
    location / {
        try_files "/storage/$host/GET$uri?.html" "/storage/$host/GET$uri/index.html" $uri $uri/ /index.php?$query_string;
    }
    Security headers for cached hits must then come from nginx (add_header) since the PHP middleware won't run.
  2. Clear + warm the cache on deploy with real requests (carries the right Host header):
    php artisan static:clear
    curl -s https://markvaneijk.com/sitemap.xml | grep -oP '(?<=<loc>)[^<]+' | xargs -n1 curl -s -o /dev/null

Upstream findings (backstagephp/laravel-static)

Bugs worth fixing in the package itself:

  • config('static.files.include_domain') is ignored on writes — createStaticFile() hardcodes $request->getHost() into the path (basePath() honors the flag but is never used there).
  • php artisan static:build creates synthetic requests via Request::create($route->uri()), so all files land under localhost/ regardless of APP_URL — combined with the above, build output can never be served for a real domain. On-demand caching is unaffected (hence the curl warm-up above).
  • With on_termination: true, static:build writes nothing at all — the build dispatches via the router, so middleware terminate() never fires.
  • README installation docs are stale: the laravel-static-config publish tag doesn't exist (publishing works via the provider), and the namespace is Backstage\Static\Laravel, not Backstage\LaravelStatic.
  • Consider supporting spatie/crawler ^9 so consumers can use laravel-sitemap ≥ 8.0.1 (v9 is a full API rewrite: Crawler::create(url), renamed fluent methods, new observer signatures).

🤖 Generated with Claude Code

markvaneijk and others added 2 commits July 3, 2026 14:41
- Replace spatie/laravel-sitemap with a small Blade template: the site only
  hand-builds a 5-URL sitemap, and laravel-sitemap's spatie/crawler ^9
  requirement conflicted with laravel-static's ^8
- Apply StaticResponse middleware to the HTML routes (home, uses, now,
  aliases, posts index, post pages); sitemap, feed and socials stay dynamic
- Config: routes driver, query strings ignored (pages don't vary by query
  string, and it prevents cache-stuffing via ?ref= URLs), minified HTML
- Clear cached pages when a Post is saved or deleted through the model;
  markdown files added directly to content/posts need a rebuild on deploy
- STATIC_ENABLED=true by default, disabled in local .env

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
laravel-sitemap 8.0.0 is the newest version still on spatie/crawler ^8,
so it can coexist with backstage/laravel-static; 8.0.1+ require crawler
^9. Pinned exactly until laravel-static supports crawler v9.

Cache invalidation on Post events removed — posts are markdown files in
content/posts, so model events never fire for real publishes anyway;
clear/warm the static cache on deploy instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant