spatie/laravel-export
Export a Laravel app as a static site bundle. Crawls your routes to generate HTML for every URL and copies your public assets into an export folder, ready to upload to Netlify or any static host—keep Laravel tools locally while deploying static.
Installation
composer require spatie/laravel-export
php artisan vendor:publish --provider="Spatie\Export\ExportServiceProvider"
Publish the config file to customize default paths (config/export.php).
First Export
php artisan export
Outputs static HTML files to storage/app/export (default) with all crawled routes and the public directory.
Where to Look First
config/export.php (adjust paths, routes, and crawling behavior).Spatie\Export\Crawler\Crawler in your app for custom route filtering.app/Console/Kernel.php to bind custom commands or extend the default export command.First Use Case Export a blog built with Laravel + Filament:
php artisan export --routes="posts.*,admin.posts.*" --exclude-routes="admin.*"
Generates static HTML for all posts.* routes while excluding admin routes.
Route-Based Exporting
routes/web.php (excluding GET routes with middleware('web') by default).// In config/export.php
'routes' => [
'include' => ['posts.index', 'about'],
'exclude' => ['admin.*', 'dashboard'],
],
php artisan export --routes="home,blog.*"
Asset Handling
public directory to the export folder.Spatie\Export\AssetHandler to include additional files (e.g., vendor assets):
public function handle(): void
{
$this->copyDirectory(public_path(), $this->exportPath);
$this->copyDirectory(base_path('vendor/your-package/public'), $this->exportPath.'/vendor');
}
Caching and Incremental Exports
Spatie\Export\Middleware\CacheResponses) to avoid re-rendering on every export.Spatie\Export\Contracts\Exportable to skip unchanged routes:
public function shouldExport(Request $request): bool
{
return !Cache::has("exported_{$request->path()}");
}
Integration with Admin Panels
Route::get('/export-posts', function () {
return response()->json(Post::all()->toArray());
})->middleware(['web', 'exportable']); // Custom middleware to allow export
--api flag to crawl API routes:
php artisan export --api
Local Development to Static Hosting
php artisan export --directory=build
build/ to Netlify/Vercel/GitHub Pages.# .github/workflows/export.yml
jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: composer install
- run: php artisan export --directory=build
- uses: actions/upload-artifact@v3
with:
name: static-site
path: build/
Dynamic Content Leaks
// app/Http/Middleware/ExportSanitizer.php
public function handle(Request $request, Closure $next)
{
if ($request->hasHeader('X-Export')) {
$request->merge(['user' => User::find(1)]); // Mock user
}
return $next($request);
}
config/export.php:
'middleware' => [
\App\Http\Middleware\ExportSanitizer::class,
],
Route Crawling Quirks
Spatie\Export\Crawler\Crawler:
public function resolveRouteBinding(string $parameter, string $value)
{
if ($parameter === 'post') {
return Post::findOrFail(1); // Mock post
}
return parent::resolveRouteBinding($parameter, $value);
}
Asset Paths Break
/storage/) fail in static exports.AssetHandler:
public function getAssetUrl(string $path): string
{
return str_replace(public_path(), '', $path);
}
Database Connections
php artisan export --database=sqlite_testing
config/export.php:
'database_connection' => env('EXPORT_DB_CONNECTION', 'sqlite_testing'),
SEO and Meta Tags
spatie/laravel-seo-tools to generate static meta files.Dry Run Mode
Use --dry-run to log routes without exporting:
php artisan export --dry-run
Verbose Output
Enable debug mode in config/export.php:
'debug' => env('APP_DEBUG', false),
Custom Exporter Class
Extend Spatie\Export\Exporters\HtmlExporter to log or validate exports:
public function exportRoute(Route $route)
{
Log::info("Exporting route: {$route->uri}");
return parent::exportRoute($route);
}
Testing Exports
Use Spatie\Export\Testing\TestsExport trait in PHPUnit:
use Spatie\Export\Testing\TestsExport;
class ExportTest extends TestCase
{
use TestsExport;
public function test_export_contains_homepage()
{
$this->assertExportedRouteExists('/');
}
}
Custom Exporters Create a new exporter for non-HTML formats (e.g., PDF):
class PdfExporter implements Exporter
{
public function export(Route $route)
{
$pdf = PDF::loadView('pdf.template', ['route' => $route]);
return $pdf->save(storage_path("export/{$route->uri}.pdf"));
}
}
Register in config/export.php:
'exporters' => [
\App\Exporters\PdfExporter::class,
],
Pre/Post Export Hooks Use events to run logic before/after export:
// In EventServiceProvider
protected $listen = [
'export.starting' => [\App\Listeners\BackupDatabase::class],
'export.finished' => [\App\Listeners\NotifySlack::class],
];
Custom Crawlers
Override Spatie\Export\Crawler\Crawler to implement custom logic (e.g., skip authenticated routes):
public function shouldCrawl(Route $route): bool
{
return !collect($route->middleware())->contains('auth');
}
Static Site Generators
Integrate with tools like laravel-static-page-generator or spatie/laravel-static-page-generator for advanced use cases (e.g., Markdown support).
How can I help you explore Laravel packages today?