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

Static Site Generator Laravel Package

statamic/static-site-generator

Laravel/Statamic addon that generates a static HTML version of your site with a single command. Configure routes, exclude pages, and deploy the output to any static host or CDN for fast, secure delivery—no PHP or database needed at runtime.

View on GitHub
Deep Wiki
Context7

Getting Started

Install (build-time dependency, not runtime).

composer require statamic/ssg

This is a first-party Statamic addon that auto-discovers its service provider. It does nothing at request time — it only adds a generation command. Many teams install it with --dev; if you do, remember your CI build step must run composer install without --no-dev or the command won't exist when the pipeline needs it.

Publish and read the config first.

php artisan vendor:publish --provider="Statamic\StaticSite\ServiceProvider"

Open config/ssg.php and confirm the keys on your installed version (they've drifted slightly across majors). The four that matter day-to-day: base_url, destination, exclude, and copy/filesystems. Treat the rest as advanced.

Set an absolute base URL. This is the single highest-value five minutes you'll spend. It should equal the URL the site will actually be served from in production — not localhost, not a preview domain. Every <link>, <img src>, canonical, and sitemap entry is generated from it.

Generate.

npm run build          # Vite manifest must exist — see Gotchas
php please ssg:generate

(php artisan ssg:generate works identically; please is just Statamic's alias.)

Verify before you trust it. Serve the output directory directly, not through Laravel:

npx serve build   # or: php -S localhost:8000 -t build

Browsing the static build with the PHP app stopped is the fastest way to find baked-in request-time assumptions. If a page renders, a form posts, or a search box works only while php artisan serve is running, that's a bug in your static story, and you'll see it here.

First real use case: point it at a marketing site or blog, generate, upload build/ to Netlify/S3/Cloudflare Pages, and confirm the whole thing works with PHP switched off. That's the go/no-go experiment.

Implementation Patterns

The local loop. Generation reads from your local Statamic content and writes to build/. Keep build/ gitignored, and regenerate rather than hand-editing anything inside it. If you're iterating on templates, this loop is ~seconds for small sites and gets slower as page count grows — budget for it.

URL discovery is crawl-based, so your exclude/include lists are load-bearing. The generator walks internal links from the root and renders what it finds. Anything unlinked will be missed, and anything linked will be published whether you want it or not. Establish rules early:

'exclude' => ['/cp', '/search', '/preview', '/forms/*', '/account/*'],
'include' => ['/404', '/rss.xml', '/sitemap.xml'],

Exclude the control panel, preview routes, POST endpoints, and anything authenticated. Explicitly include hand-written routes that nothing links to — a 404 page and feed endpoints are the classic omissions.

Pass-through files. Files Laravel doesn't route (a hand-authored robots.txt, a Netlify _redirects, a _headers, favicons, verification files) need to be copied into the build. Use the config's copy mechanism rather than dumping them in public/ and hoping; the destination directory is generated, so anything you place there manually vanishes on the next clean build.

Custom destination / S3. destination accepts a path or a configured filesystem disk. Pushing straight to a bucket from CI is a legitimate pattern — combine it with the clear/wipe behavior so deleted pages don't accumulate at the edge.

CI is where this package actually lives. A typical workflow:

- composer install          # no --no-dev if SSG is a dev dependency
- npm ci && npm run build
- php please stache:warm    # ensure latest content is indexed
- php please ssg:generate
- run link checker / assert page count
- deploy build/ to CDN + purge cache

Trigger it from a Statamic publish webhook or a schedule, and make the cache invalidation part of the same job. A successful build that leaves stale HTML at the CDN is the most common "it didn't deploy" report you'll get.

Multisite. Each site needs its own generation pass with its own base URL and destination (per-site directory or per-site disk). Don't assume one run covers all sites — verify localized URL generation explicitly, since that's where broken links concentrate.

Replacing what you lose. Decide these per-project before you commit:

  • Forms → Netlify Forms, Formspark, Basin, or an edge function. The POST route no longer exists.
  • Search → Pagefind (build-time index, no vendor), Algolia, or Typesense.
  • Redirects → move from Laravel middleware into host-level _redirects / redirects.json.
  • Auth/personalization → out of scope; if the product needs it, this is the wrong delivery model.

Validate the build as a deploy gate. A page-count assertion plus a link checker catches 90% of regressions. Without it, a bad exclude silently drops a section and nobody notices until a customer does.

Gotchas and Tips

base_url mismatch is the #1 cause of broken output. Symptom: correct-looking HTML locally, every asset and link pointing at http://localhost in the deployed build. Check this before debugging anything else. Related trap: changing APP_URL for the build but leaving hardcoded absolute URLs in Antlers.

Assets must be compiled before generation. If you generate with npm run dev running, the @vite directive can emit HMR/dev-server script tags that don't exist in production. Always npm run build first, and confirm the manifest is present in the environment doing the generation.

Stale output is silent. If clearing isn't enabled, pages you deleted or renamed keep being served forever from the destination directory/CDN. Turn clearing on, or make "wipe destination" an explicit CI step — and remember clearing doesn't purge your CDN.

Baked-in request-time state. Things that quietly break: {{ nocache }} blocks (emit nothing useful), CSRF tokens (frozen and invalid), flash messages, session-based logic, request() calls in templates, and Statamic live preview. Grep your templates for request(, csrf, and nocache before your first build.

Time is frozen at build time. now, "posted 3 hours ago", and scheduled/future-dated content are all snapshots. Relative timestamps go stale immediately, and scheduled publishing requires a rebuild — which means your publish cadence is your content cadence.

Crawl discovery misses things. Paginated archives (page 2+ unreachable without following "next"), pages behind JS-only navigation, and sitemap-only routes. If a page exists but nothing links to it in the HTML, it won't be generated. Add it to include.

Trailing-slash and index.html semantics differ per host. /about/ → about/index.html vs /about → about.html is a host decision. Pick one and make sure your internal links, generator output, and host config agree, or you'll get redirect loops and 404s on deep links.

404s need explicit wiring. Generate a /404 page and configure the host's not-found fallback to it. Static hosts don't route to a framework error handler.

Build environment matters. Run with APP_DEBUG=false and production-like settings — debug error pages get written to disk as your content. Also clear/warm the Stache before generating, or you'll ship a build missing the editor's most recent changes.

Build time is roughly linear in page count. Full rebuild per publish, no incremental mode. Tens of thousands of pages turns a five-minute publish into a pipeline design problem — know your number before you commit.

Version gate. 4.1.0 is the release that adds Laravel 13 support; it's a clean minor from 4.0.0 with no config or data migration (the PHPUnit attribute change is test-only). If your host app is moving to Laravel 13, this bump is a prerequisite, not an optional upgrade — do it in a non-production environment and regenerate a build to confirm output parity.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource