bakslash/static-site-bundle
A Symfony bundle for exporting your application as a static site. It crawls configured routes or uses a URL list, saves rendered HTML to disk, and helps automate static builds for fast, low-cost hosting.
Architecture fit. This is a low-ceremony, opt-in layer over the framework's existing routing and controller stack rather than a parallel templating system. Marking controllers with #[Prerender] at method or class level means the pages we already serve dynamically can be emitted statically without a rewrite — the controller, its DI dependencies, its Blade/Inertia output and its middleware all remain the single source of truth. For a TPM this is the most attractive property of the package: it defers the "do we build a separate static site generator?" decision and keeps one codebase and one review surface. The ParamsProviderInterface and inline parameter-set features cover the catalogue/listing case (blog posts, product pages) that usually forces teams into a second tool.
Integration feasibility. Superficially a same-day spike: install, annotate a handful of controllers, run static-site:list to preview, static-site:generate to write. Output targets a local directory or any Flysystem operator, so S3/FTP delivery reuses disks we almost certainly already have configured, and there is no bespoke deploy code. clean_urls removes a whole class of web-server configuration work (<path>/index.html) and makes the artifact portable across hosts — useful if we ever move off the current CDN/edge provider.
Technical risk. Several items need closing before this is more than a spike:
symfony/* versions already locked in our app. This is the single most likely blocker and should be checked first.sensiolabs/minify-bundle; we need to confirm it coexists with our existing asset pipeline rather than double-minifying.Key questions for the maintainer / spike.
Stack fit. Assumes a modern Laravel app on PHP 8.3 with Flysystem disks already configured (S3 for production, local for CI artifacts). Nothing in the release suggests a need for new infrastructure: the build is a console command, the artifact is a directory tree, delivery is an existing disk. That keeps the change inside the application repo and out of platform work.
Migration path. Do not treat this as a cutover. The natural sequence is:
composer require and resolve the Symfony/Laravel matrix. If it will not install against our lockfile, the evaluation ends here — that is the cheapest possible failure.base_uri and confirm clean_urls output serves correctly from a plain file server.ParamsProviderInterface for the high-cardinality content (posts, products). Measure build wall-clock and output size at realistic cardinality — this is where the package will either scale or not.PageRenderedEvent/PageDumpedEvent. Use PageFailedEvent to fail the CI job loudly rather than ship a partial site.Compatibility. Verify against the actual lockfile, not the release notes. Specifically: symfony/* pins, whether sensiolabs/minify-bundle collides with existing minifiers, whether our controllers depend on request state (session, locale from cookies) that will not exist at build time, and whether base_uri misconfiguration produces silently wrong absolute URLs rather than an error.
Sequencing. The dependency spike gates everything; the parameterised-route pilot gates the business case. If step 3 shows build times that scale badly and there is no incremental mode, the package is still viable for a small marketing site but not as a wholesale replacement for dynamic rendering.
Maintenance. Pre-1.0 means upgrade churn is a certainty, not a risk. The surface we touch is small (attributes, two commands, optionally one provider), so our own migration cost per bump is bounded — but any custom renderer/dumper/URI-provider/filesystem swaps bind us to internal service IDs and should be treated as a fork we own. Pin the exact version; budget an upgrade review per minor release.
Support. Assume a small maintainer team. Mitigation is favourable: the extension points, the fact that it sits on our own controllers, and the fact that the output is plain files mean we can vendor or fork the package without stranding the site. The artefact is recoverable — worst case we redeploy the dynamic site from the same git SHA.
Scaling. Read path is trivially scalable: static files behind the existing CDN, no runtime PHP, better cache-hit ratio and lower origin cost. Write path is the constraint. Build time is roughly linear in (annotated routes × parameter sets), and there is no stated incremental generation, so every run is a full rebuild. At low hundreds of pages that is minutes; at tens of thousands of product pages it becomes a scheduled job with its own capacity planning. --route/--path globs give partial-run capability, which is the lever we would use to shard builds by content type if needed.
Failure modes to design against.
base_uri — wrong absolute links, correct-looking build. Assert on a canonical sample page.PageDumpedEvent hook is the place to purge; if it fails, the site is correct on disk and wrong in front of users.Ramp-up. A competent Laravel developer is productive with the attributes and two commands in under a day; the pilot in the migration path is more about validating assumptions than learning the API. The real ramp-up cost sits outside the package: teaching the team that publishing now depends on a build pipeline, that some content is intentionally stale, and that cache invalidation is part of "done". Plan for one owner of the build job and a documented runbook (how to trigger, how to roll back, how to verify) before the first production route is served statically.
How can I help you explore Laravel packages today?