Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Export Laravel Package

spatie/laravel-export

Turn a Laravel app into a static site bundle. Laravel Export crawls every URL, renders HTML pages, copies your public assets, and saves a ready-to-host export with one artisan command—ideal for blogs and admin-managed sites published to static hosting.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

spatie/laravel-export is not a static-site generator in the Jekyll/Astro sense — it is a renderer of an already-running Laravel app. The mechanism is a crawl: the package boots an HTTP client against your application, follows internal links, and writes each response body to a disk as an HTML file, then mirrors public/ (or any include_files mapping) alongside it. That distinction drives the entire evaluation. It fits cleanly when the app is fundamentally a read-mostly content site (blog, marketing pages, docs, catalogue) where every page is addressable by a stable GET URL and renders server-side. It fits poorly when the value of the app is interactive, session-scoped, or client-rendered.

The architecture is thin and layered on well-known Spatie primitives: spatie/crawler for traversal, Laravel's Filesystem abstraction for output, and symfony/process for hooks. There is no proprietary runtime, no templating dialect, no plugin API. For a TPM this is the single most important finding: the package is a convenience wrapper, not a platform. Lock-in is near zero and an exit to a bespoke Artisan command is measured in days, not quarters.

Two design details materially improve fit. First, Exporter is injectable and exposes the content-affecting options (crawl(), paths(), useStreaming()) in code, so route enumeration can be driven from the database rather than a hand-maintained config array — this is what makes it viable for content sites with hundreds of records. Second, the X-Laravel-Export header (added 1.0.0, corrected to apply on the export path in 1.1.1) gives the application a reliable signal that a request is an export render, enabling you to suppress analytics, disable CSRF expectations, or swap to a stripped layout without branching on user agent.

Integration feasibility

Low-to-moderate effort. Installation is one composer require plus an optional config publish; the default config is functional out of the box. The realistic work is not the package, it is the application remediation required to make every page exportable: removing auth gates from otherwise-public content, ensuring routes are GET and idempotent, eliminating writes-on-read (view counters, "last seen" timestamps, session flash) that make output non-deterministic, and deciding how paginated or faceted URLs are enumerated. Budget the integration at roughly 1–2 weeks for a small site with a clean route table, and 3–6 weeks for a mature app with authenticated areas, dynamic query parameters, or client-side rendering anywhere in the critical path.

Technical risk

  • Crawl-based discovery is the primary risk. Anything not linked from a crawlable page is silently absent from the bundle. Orphaned routes must be added via paths, and there is no coverage assertion telling you the export is complete. A missing page is a 404 in production, discovered by a user.
  • Authenticated and personalized content. Crawling happens as an unauthenticated client by default. Multi-tenant or gated content requires either bypass logic guarded by the export header or accepting that those pages are excluded.
  • Fragility on error. Broken pages throw during crawl (since 0.3.3, with $foundOnUrl surfaced since 0.3.8). Good for correctness, but it means a single flaky route fails the whole build — desirable in CI, hostile if you ever ran it against production.
  • Client-rendered content. No headless browser involved. Anything hydrated by JavaScript at runtime exports as an empty shell unless you add SSR or a prerender step yourself.
  • Query-string handling and redirects. URL encoding for paths with query parameters landed only in 1.2.1, and redirect export in 1.2.0 — both recent, both worth an explicit test in your own pipeline rather than assumed correct.
  • Adoption signal. Zero listed dependents and a mid-30s composite score. For a build/dev-time tool this is expected (it never appears in a runtime dependency graph), so treat it as an absence of evidence, not evidence of risk. The maintainer signal is strong: continuous Laravel 9→13 compatibility, PHP 8.1/8.2 support, streaming, and a symlink crash fix as recently as 1.4.1.

Key questions

  1. Can every page we intend to publish be reached by following links from /, or do we need an explicit paths manifest sourced from the database?
  2. Does any target page depend on session state, authentication, or a per-user variant?
  3. Is anything rendered client-side that a crawler would miss?
  4. What is our completeness contract — how do we detect a page that should exist but wasn't exported?
  5. Do we need redirects, non-HTML artifacts (/rss.xml, sitemap.xml, robots.txt), or binary files preserved — and are those covered by paths and include_files?
  6. Is the output destined for a CDN/object store, and does the target's path semantics (trailing slash, index.html resolution) match what the package writes?

Integration Approach

Stack fit

Requires a Laravel app on a supported major (10 through 13 are all covered by the 1.1.x–1.3.x line) with PHP 8.1+. It is a require-dev dependency in spirit — it should never ship to production runtime — and its natural home is a CI job, not a developer laptop and certainly not the web server. Output targets any Laravel filesystem disk, so local for artifact upload or an S3 driver for direct-to-bucket publishing are both first-class; the README explicitly supports the S3 path.

Migration path

The package is additive, so there is no migration in the schema sense. The meaningful decision is what role the Laravel app retains. Two viable models:

  • Static-primary. The app becomes an authoring surface (Filament, Nova, Wink) that runs in a private/staging environment; the public artifact is the exported bundle. This is the model the README endorses and where the package earns its keep.
  • Hybrid. Dynamic for logged-in or transactional routes, static for the public content surface. This works but requires disciplined route segmentation and a CDN rule set that routes by path prefix. It is the more common real-world outcome and the one most likely to produce operational drift.

Compatibility

Verify three things early, in a spike, before committing: (1) the exact Laravel major vs the package line — 1.3.0 for Laravel 13, 1.1.2 for Laravel 12; (2) spatie/crawler ^9.1, which is pulled by 1.4.0 and later and is where streaming originates, so pinning to an older export release removes the memory relief; (3) the target host's handling of extensionless URLs and trailing slashes, which is a hosting concern rather than a package concern but determines whether the bundle works at all.

Sequencing

  1. Spike (days). Scaffold the export locally against a representative slice of routes. Measure bundle size, crawl duration, and peak memory. Confirm asset paths resolve.
  2. Route inventory and remediation. Classify every route as static-eligible, needs-bypass, or excluded. Remove writes-on-read from static-eligible pages. Decide the paths manifest strategy and implement it via the injectable Exporter in a service provider so it stays in sync with the database.
  3. Render-mode conditioning. Use the X-Laravel-Export header to strip analytics, disable CSRF-dependent forms, and force canonical URLs, so export renders don't diverge from production renders in ways that only surface in the bundle.
  4. Pipeline. Add the before hook for asset compilation (npm run build / Yarn) and the after hook for deploy. Keep hook commands in config-as-code, not committed config files with machine-specific absolute paths. In CI, prefer --skip-deploy on PR builds and the full chain only on merge to the release branch.
  5. Verification gate. Add a post-export assertion step — link checking, sitemap diffing against the route manifest, and a byte-size sanity check on the bundle — because the package itself offers no completeness guarantee.
  6. Cutover and decommission. Point DNS/CDN at the static origin, keep the dynamic app alive for authoring, and retire the public web tier.

Operational Impact

Maintenance

Ongoing cost is low and mostly external: dependency bumps on Laravel majors, and re-validation whenever the target host changes path semantics. Internally, the real maintenance burden is the paths manifest and render-mode conditioning — both live in your codebase and both rot if contributors add routes without knowing the export exists. Encode that as a CI check (export succeeds + link check passes) rather than a convention.

Support

MIT licensed with active, recent maintenance and a visible changelog. Support depth is realistically "read the source" — the package is small enough to audit in an afternoon, which is the correct mitigation for the thin maintainer bandwidth typical of a Spatie side project. Treat Postcardware as a courtesy, not a support contract. Escalation path is the GitHub tracker plus, if it ever matters, your own fork; the surface area is small enough that forking remains feasible indefinitely.

Scaling

Scales with page count and crawl time, not with traffic — and traffic scaling is the entire point, since the artifact is served by a CDN and the app tier is no longer in the request path. Enable use_streaming before the site grows; it is available from 1.4.0 and is the difference between a build that fits in a CI runner and one that OOMs. For large sites, crawl concurrency matters: you are hammering your own application, so run the export against a dedicated environment or replica, never the production pool. Note also that the crawl is inherently sequential in output correctness terms — no incremental builds, no cache, full re-render every time. A 5,000-page site will mean a 5,000-request build; measure that early because it eventually forces a sharded or scheduled export strategy.

Failure modes

  • Silent omission. An unlinked route is simply absent. Highest-severity, lowest-visibility failure. Mitigate with manifest-diff assertions.
  • Build abort on a single bad page. Fail-fast on crawl errors means one flaky route blocks all publishing. Correct for CI, but it couples release cadence to route health.
  • Non-deterministic output. Time-stamped content, counters, randomized ordering, and relative-date formatting produce noisy diffs and make "did the deploy change anything?" unanswerable.
  • Hook environment mismatch. Hooks are shell commands with absolute paths in the examples (/usr/local/bin/yarn). These break across CI images and developer machines; the --skip-before/--skip-after/--skip-all flags exist precisely because hooks are environment-sensitive and should be used deliberately in CI.
  • Auth/CSRF regressions. If the export render path depends on middleware that behaves differently without a session, pages can export as redirects to login or as 419s — visible only in the bundle.
  • Stale artifact. No built-in cache invalidation or CDN purge; publishing success does not imply serving success. Couple the deploy hook with a purge or content-hash assertion.

Ramp-up

A mid-level Laravel engineer can be productive in under a day; the API surface is a config array, one Artisan command, and one injectable class. The ramp-up cost that actually matters is organizational: content authors need to understand that publishing is a build, with latency and a failure surface, rather than a save. Budget a short runbook covering how to trigger a publish, how to read a failed crawl, and what to do when a page is missing from the bundle.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder
ideatica/config-writer
keaton-php/pipe-dream
jacksonwebservices/laravel-user-suite
maksimru/conditional-laravel-debugbar
gismatul/nova-support-chat
gismatul/laravel-support-chat
networkrailbusinesssystems/oracle-api
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
refinery29/php-cs-fixer-config
parsnick/steak