spatie/laravel-export
Export a Laravel app as a static site bundle. Crawls your routes, renders HTML for each discovered URL, and copies the public directory so assets are included. Ideal for blogs and marketing sites hosted on Netlify or any static hosting.
Installation:
composer require spatie/laravel-export
No additional configuration is required to start exporting.
First Export:
php artisan export
This generates a static site bundle in the export directory (default) with all crawled pages and the public folder contents.
Verify Output:
export directory for generated HTML files.public/ are included.Export a Blog with Dynamic Content:
Post model and routes like /posts/{slug}).php artisan export to generate static HTML for all blog posts.export folder to a static host (e.g., Netlify, S3).Crawling:
config/export.php:
'paths' => [
'/',
'/posts/{slug}',
'/about',
],
Exporter class (e.g., in a service provider):
$exporter->paths(Post::all()->pluck('slug')->toArray());
Asset Handling:
public folder. Customize with:
'include_files' => [
'public' => '',
'custom-assets' => 'assets',
],
mix-manifest.json):
'exclude_file_patterns' => [
'/\.php$/',
'/mix-manifest\.json$/',
],
Hooks for Automation:
'before' => [
'build-assets' => 'npm run build',
],
'after' => [
'deploy' => 'netlify deploy --prod',
],
php artisan export --skip-before --skip-after
Custom Disks:
// config/filesystem.php
'export' => [
'driver' => 's3',
'key' => 'your-key',
'secret' => 'your-secret',
'bucket' => 'your-bucket',
],
Dynamic Routes:
Use the Exporter class to dynamically generate paths for resources like blog posts or products:
$exporter->paths(Post::query()->pluck('slug')->toArray());
Conditional Exports: Add logic to exclude draft content or private routes:
$exporter->paths(
Post::where('published_at', '<=', now())
->pluck('slug')
->toArray()
);
Testing:
Mock the Exporter in unit tests to verify paths or hooks:
$exporter = $this->app->make(Exporter::class);
$exporter->shouldReceive('paths')->with(['/test']);
Circular References:
/posts/{slug} linking to /posts/{slug}/edit). Use exclude_file_patterns or middleware to block admin routes:
'exclude_file_patterns' => [
'/admin/',
],
Asset Paths:
/css/style.css) may break in static exports. Use Laravel’s asset() helper or mix-manifest.json for dynamic paths.Middleware Conflicts:
if (!$request->header('X-Laravel-Export')) {
// Apply middleware logic
}
Streaming Memory Issues:
'use_streaming' => true,
php artisan export --verbose.Symlinks and Non-Files:
IncludeFile logic or updating the config:
'exclude_file_patterns' => [
'/\.php$/',
'/mix-manifest\.json$/',
'/\.symlink$/', // Add custom patterns
],
Verbose Output:
Run exports with --verbose to debug crawling or file inclusion:
php artisan export --verbose
Dry Runs:
Test paths without writing files by temporarily changing the disk to null:
// config/filesystem.php
'export' => [
'driver' => 'null',
],
Hook Failures:
npm for asset builds).Broken Links:
link-checker hook (e.g., with HTML Link Checker).Custom Crawlers:
Extend the Spatie\Crawler\Crawler class to add logic (e.g., skip routes with X-Robots-Tag: noindex).
Post-Export Processing:
Use the after hook to run custom scripts (e.g., image optimization):
'after' => [
'optimize-images' => 'php artisan image-optimize',
],
Dynamic Headers:
Override the X-Laravel-Export header in middleware to conditionally include/exclude routes:
if ($request->header('X-Laravel-Export')) {
// Allow static export
}
Custom File Inclusion:
Create a custom IncludeFile class to handle non-standard file types (e.g., Markdown files):
use Spatie\Export\IncludeFile;
class CustomIncludeFile extends IncludeFile
{
public function handle($path)
{
if (str_ends_with($path, '.md')) {
return $this->convertMarkdownToHtml($path);
}
return parent::handle($path);
}
}
Register it in the service provider:
$this->app->bind(IncludeFile::class, CustomIncludeFile::class);
Exclude Unnecessary Routes: Limit paths to essential routes to reduce crawl time:
'paths' => [
'/',
'/posts/{slug}',
],
Parallel Exports: Use Laravel queues to run exports asynchronously for large sites:
// In a command or job
Export::dispatch();
Cache Crawled URLs: Cache the list of crawled URLs to avoid re-scanning:
$exporter->crawlCache()->rememberFor(minutes: 60);
How can I help you explore Laravel packages today?