{"id":19756422,"url":"https://github.com/backstagephp/laravel-static","last_synced_at":"2026-03-09T12:04:30.482Z","repository":{"id":60663440,"uuid":"543986426","full_name":"backstagephp/laravel-static","owner":"backstagephp","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-20T08:16:54.000Z","size":88,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-01-20T18:06:26.840Z","etag":null,"topics":["backstage","cache","filament","laravel","php","static"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/backstagephp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"backstagephp"}},"created_at":"2022-10-01T10:34:34.000Z","updated_at":"2026-01-20T08:16:58.000Z","dependencies_parsed_at":"2023-01-31T11:01:27.682Z","dependency_job_id":"5e564d2a-e34d-461e-b6f8-dc92f740bcdc","html_url":"https://github.com/backstagephp/laravel-static","commit_stats":{"total_commits":19,"total_committers":4,"mean_commits":4.75,"dds":0.1578947368421053,"last_synced_commit":"f05adf62033dbedf5124aad04846112364292bc6"},"previous_names":["backstagephp/laravel-static","vormkracht10/laravel-static"],"tags_count":10,"template":false,"template_full_name":"backstagephp/laravel-package-template","purl":"pkg:github/backstagephp/laravel-static","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backstagephp%2Flaravel-static","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backstagephp%2Flaravel-static/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backstagephp%2Flaravel-static/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backstagephp%2Flaravel-static/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/backstagephp","download_url":"https://codeload.github.com/backstagephp/laravel-static/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backstagephp%2Flaravel-static/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30294713,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-09T11:12:22.024Z","status":"ssl_error","status_checked_at":"2026-03-09T11:10:54.577Z","response_time":61,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backstage","cache","filament","laravel","php","static"],"created_at":"2024-11-12T03:15:53.506Z","updated_at":"2026-03-09T12:04:30.477Z","avatar_url":"https://github.com/backstagephp.png","language":"PHP","funding_links":["https://github.com/sponsors/backstagephp"],"categories":[],"sub_categories":[],"readme":"# Laravel Static\n\n[![Total Downloads](https://img.shields.io/packagist/dt/backstage/laravel-static.svg?style=flat-square)](https://packagist.org/packages/backstage/laravel-static)\n[![Tests](https://github.com/backstagephp/laravel-static/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://github.com/backstagephp/laravel-static/actions/workflows/run-tests.yml)\n[![PHPStan](https://github.com/backstagephp/laravel-static/actions/workflows/phpstan.yml/badge.svg?branch=main)](https://github.com/backstagephp/laravel-static/actions/workflows/phpstan.yml)\n![GitHub release (latest by date)](https://img.shields.io/github/v/release/backstagephp/laravel-static)\n![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/backstage/laravel-static)\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/backstage/laravel-static.svg?style=flat-square)](https://packagist.org/packages/backstage/laravel-static)\n\n**Supercharge your Laravel application with static file caching.** Laravel Static converts your dynamic Laravel responses into static HTML files, dramatically improving performance and reducing server load.\n\n## Why Laravel Static?\n\nTraditional Laravel applications generate HTML on every request, hitting your database and executing PHP code repeatedly. Laravel Static solves this by:\n\n- **Converting dynamic responses to static HTML files** — Serve pre-generated HTML instead of executing PHP on every request\n- **Reducing server load** — Let your web server (Nginx, Apache) serve static files directly\n- **Improving response times** — Static files are served in milliseconds, not hundreds of milliseconds\n- **Supporting multiple caching strategies** — Choose between route-based caching or automatic web crawling\n- **Handling complex scenarios** — Multi-domain support, query string handling, and HTML minification\n\n## Requirements\n\n- PHP 8.1 or higher\n- Laravel 11.0 or higher\n\n## Installation\n\nInstall the package via Composer:\n\n```bash\ncomposer require backstage/laravel-static\n```\n\nPublish the configuration file:\n\n```bash\nphp artisan vendor:publish --tag=\"laravel-static-config\"\n```\n\nOptionally, publish the migrations if you need database-backed features:\n\n```bash\nphp artisan vendor:publish --tag=\"laravel-static-migrations\"\nphp artisan migrate\n```\n\n## Quick Start\n\n### 1. Enable Static Caching\n\nAdd the `STATIC_ENABLED=true` environment variable to your `.env` file:\n\n```env\nSTATIC_ENABLED=true\n```\n\n### 2. Add Middleware to Routes\n\nApply the `StaticResponse` middleware to routes you want to cache:\n\n```php\nuse Backstage\\LaravelStatic\\Middleware\\StaticResponse;\n\nRoute::get('/', function () {\n    return view('welcome');\n})-\u003emiddleware(StaticResponse::class);\n\n// Or apply to route groups\nRoute::middleware([StaticResponse::class])-\u003egroup(function () {\n    Route::get('/about', [PageController::class, 'about']);\n    Route::get('/contact', [PageController::class, 'contact']);\n    Route::get('/blog', [BlogController::class, 'index']);\n});\n```\n\n### 3. Build the Static Cache\n\nGenerate your static files:\n\n```bash\nphp artisan static:build\n```\n\nThat's it! Your routes are now served as static HTML files.\n\n## Configuration\n\nThe configuration file is located at `config/static.php`. Here's a breakdown of all available options:\n\n### Caching Driver\n\n```php\n'driver' =\u003e 'crawler', // Options: 'crawler' or 'routes'\n```\n\n| Driver | Description |\n|--------|-------------|\n| `crawler` | Uses Spatie Crawler to automatically discover and cache all internal URLs starting from your homepage. Best for sites with many interconnected pages. |\n| `routes` | Only caches routes that have the `StaticResponse` middleware explicitly applied. Best for selective caching. |\n\n### Enable/Disable\n\n```php\n'enabled' =\u003e env('STATIC_ENABLED', true),\n```\n\nToggle static caching on or off. Useful for disabling in development while keeping it enabled in production.\n\n### Build Settings\n\n```php\n'build' =\u003e [\n    'clear_before_start' =\u003e true,    // Clear existing cache before rebuilding\n    'concurrency' =\u003e 5,               // Number of concurrent HTTP requests\n    'accept_no_follow' =\u003e true,       // Follow nofollow links when crawling\n    'default_scheme' =\u003e 'https',      // URL scheme for crawler requests\n    'crawl_observer' =\u003e \\Backstage\\LaravelStatic\\Crawler\\StaticCrawlObserver::class,\n    'crawl_profile' =\u003e \\Spatie\\Crawler\\CrawlProfiles\\CrawlInternalUrls::class,\n    'bypass_header' =\u003e [\n        'name' =\u003e 'X-Laravel-Static',\n        'value' =\u003e 'off',\n    ],\n],\n```\n\n### File Storage\n\n```php\n'files' =\u003e [\n    'disk' =\u003e env('STATIC_DISK', 'public'),  // Laravel filesystem disk\n    'include_domain' =\u003e true,                 // Create separate caches per domain\n    'include_query_string' =\u003e true,           // Include query strings in cache keys\n    'filepath_max_length' =\u003e 4096,            // Maximum file path length\n    'filename_max_length' =\u003e 255,             // Maximum filename length\n],\n```\n\n### Additional Options\n\n```php\n'options' =\u003e [\n    'on_termination' =\u003e false,  // Save cache after response sent (async)\n    'minify_html' =\u003e false,     // Minify HTML before caching\n],\n```\n\n## Commands\n\n### Build Static Cache\n\nGenerate static files for all configured routes:\n\n```bash\nphp artisan static:build\n```\n\nWhen using the `routes` driver, only routes with the `StaticResponse` middleware are cached. When using the `crawler` driver, the crawler starts from your homepage and discovers all internal links.\n\n### Clear Static Cache\n\nClear all cached static files:\n\n```bash\nphp artisan static:clear\n```\n\nClear specific URIs:\n\n```bash\nphp artisan static:clear --uri=/about --uri=/contact\n```\n\nClear by route names:\n\n```bash\nphp artisan static:clear --routes=home --routes=about --routes=blog.index\n```\n\nClear by domain (useful for multi-tenant applications):\n\n```bash\nphp artisan static:clear --domain=example.com\nphp artisan static:clear --domain=subdomain.example.com\n```\n\n## Advanced Usage\n\n### Multi-Domain Support\n\nLaravel Static supports multi-domain setups out of the box. When `include_domain` is enabled (default), each domain gets its own cache directory:\n\n```\nstorage/app/public/\n├── example.com/\n│   ├── GET/\n│   │   ├── index.html\n│   │   └── about.html\n├── subdomain.example.com/\n│   ├── GET/\n│   │   └── index.html\n```\n\n### Query String Handling\n\nWhen `include_query_string` is enabled, different query strings create separate cache files:\n\n```\n/products?page=1  → products/page=1.html\n/products?page=2  → products/page=2.html\n/search?q=laravel → search/q=laravel.html\n```\n\n### HTML Minification\n\nEnable HTML minification to reduce file sizes:\n\n```php\n// config/static.php\n'options' =\u003e [\n    'minify_html' =\u003e true,\n],\n```\n\nThis removes unnecessary whitespace, comments, and optimizes the HTML output using the [voku/html-min](https://github.com/voku/HtmlMin) library.\n\n### Bypass Header\n\nDuring development or testing, you may want to bypass the static cache. The package includes a bypass header mechanism:\n\n```bash\ncurl -H \"X-Laravel-Static: off\" https://example.com/\n```\n\nThis header tells the middleware to skip the static cache and generate a fresh response.\n\n### Programmatic Cache Clearing\n\nUse the `StaticCache` facade to clear cache programmatically:\n\n```php\nuse Backstage\\LaravelStatic\\Facades\\StaticCache;\n\n// Clear all cache\nStaticCache::clear();\n\n// Clear specific paths\nStaticCache::clear(['/about', '/contact']);\n```\n\n### Custom Crawl Observer\n\nCreate a custom crawl observer to customize the crawling behavior:\n\n```php\nnamespace App\\Crawlers;\n\nuse Backstage\\LaravelStatic\\Crawler\\StaticCrawlObserver;\nuse Psr\\Http\\Message\\UriInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\n\nclass CustomCrawlObserver extends StaticCrawlObserver\n{\n    public function crawled(UriInterface $url, ResponseInterface $response, ?UriInterface $foundOnUrl = null): void\n    {\n        // Add custom logic before caching\n        logger()-\u003einfo(\"Caching: {$url}\");\n\n        parent::crawled($url, $response, $foundOnUrl);\n    }\n}\n```\n\nUpdate your configuration:\n\n```php\n'build' =\u003e [\n    'crawl_observer' =\u003e \\App\\Crawlers\\CustomCrawlObserver::class,\n],\n```\n\n### Custom Crawl Profile\n\nControl which URLs get crawled by creating a custom crawl profile:\n\n```php\nnamespace App\\Crawlers;\n\nuse Psr\\Http\\Message\\UriInterface;\nuse Spatie\\Crawler\\CrawlProfiles\\CrawlProfile;\n\nclass CustomCrawlProfile extends CrawlProfile\n{\n    public function shouldCrawl(UriInterface $url): bool\n    {\n        $path = $url-\u003egetPath();\n\n        // Skip admin routes\n        if (str_starts_with($path, '/admin')) {\n            return false;\n        }\n\n        // Skip API routes\n        if (str_starts_with($path, '/api')) {\n            return false;\n        }\n\n        return true;\n    }\n}\n```\n\n### Excluding Routes from Caching\n\nRoutes with parameters cannot be automatically cached (they require specific values). You can also explicitly exclude routes by not applying the middleware:\n\n```php\n// These routes will be cached\nRoute::middleware([StaticResponse::class])-\u003egroup(function () {\n    Route::get('/', [HomeController::class, 'index']);\n    Route::get('/about', [PageController::class, 'about']);\n});\n\n// These routes will NOT be cached (no middleware)\nRoute::get('/dashboard', [DashboardController::class, 'index']);\nRoute::get('/user/{id}', [UserController::class, 'show']); // Has parameters\n```\n\n### Async Cache Generation\n\nEnable `on_termination` to generate cache files after the response is sent to the user:\n\n```php\n'options' =\u003e [\n    'on_termination' =\u003e true,\n],\n```\n\nThis improves perceived performance as users don't wait for the cache file to be written.\n\n## Web Server Configuration\n\nFor optimal performance, configure your web server to serve static files directly without hitting PHP.\n\n### Nginx\n\n```nginx\nserver {\n    listen 80;\n    server_name example.com;\n    root /var/www/html/public;\n\n    # Try static cache first, then Laravel\n    location / {\n        # Check for static cache file\n        set $cache_path /storage/example.com/GET$uri;\n\n        # Handle index files\n        if (-f $document_root$cache_path/index.html) {\n            rewrite ^ $cache_path/index.html last;\n        }\n\n        # Handle direct files\n        if (-f $document_root$cache_path.html) {\n            rewrite ^ $cache_path.html last;\n        }\n\n        # Fall back to Laravel\n        try_files $uri $uri/ /index.php?$query_string;\n    }\n\n    location ~ \\.php$ {\n        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;\n        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n        include fastcgi_params;\n    }\n}\n```\n\n### Apache\n\n```apache\n\u003cIfModule mod_rewrite.c\u003e\n    RewriteEngine On\n\n    # Check for static cache\n    RewriteCond %{DOCUMENT_ROOT}/storage/%{HTTP_HOST}/GET%{REQUEST_URI}.html -f\n    RewriteRule ^(.*)$ /storage/%{HTTP_HOST}/GET/$1.html [L]\n\n    RewriteCond %{DOCUMENT_ROOT}/storage/%{HTTP_HOST}/GET%{REQUEST_URI}/index.html -f\n    RewriteRule ^(.*)$ /storage/%{HTTP_HOST}/GET/$1/index.html [L]\n\n    # Laravel fallback\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule ^(.*)$ index.php [L]\n\u003c/IfModule\u003e\n```\n\n## Cache Invalidation Strategies\n\n### Event-Based Invalidation\n\nClear cache when content changes using model events:\n\n```php\nuse Backstage\\LaravelStatic\\Facades\\StaticCache;\n\nclass Post extends Model\n{\n    protected static function booted()\n    {\n        static::saved(function (Post $post) {\n            StaticCache::clear([\n                \"/blog/{$post-\u003eslug}\",\n                '/blog',\n            ]);\n        });\n\n        static::deleted(function (Post $post) {\n            StaticCache::clear([\n                \"/blog/{$post-\u003eslug}\",\n                '/blog',\n            ]);\n        });\n    }\n}\n```\n\n### Scheduled Rebuilds\n\nAdd a scheduled task to rebuild your cache periodically:\n\n```php\n// app/Console/Kernel.php or bootstrap/app.php (Laravel 11+)\nSchedule::command('static:build')-\u003edaily();\n```\n\n### Deploy Hook\n\nClear and rebuild cache during deployments:\n\n```bash\n#!/bin/bash\n# deploy.sh\n\nphp artisan static:clear\nphp artisan static:build\n```\n\n## Comparison: Routes vs Crawler Driver\n\n| Feature | Routes Driver | Crawler Driver |\n|---------|--------------|----------------|\n| Setup complexity | Manual (add middleware to each route) | Automatic (discovers all pages) |\n| Control | Fine-grained | Less control |\n| Speed | Faster (only caches specified routes) | Slower (crawls entire site) |\n| Discovery | Manual | Automatic |\n| Best for | Selective caching, large apps | Content sites, blogs |\n\n## How It Works\n\n1. **Request Interception**: The `StaticResponse` middleware intercepts outgoing responses\n2. **Eligibility Check**: Only `GET`/`HEAD` requests with `200 OK` status are cached\n3. **File Generation**: HTML content is saved to the configured storage disk\n4. **Optional Minification**: If enabled, HTML is minified before saving\n5. **Directory Structure**: Files are organized by domain, HTTP method, and URI path\n\nThe `PreventStaticResponseMiddleware` (automatically registered) handles bypass headers and ensures proper behavior during cache building.\n\n## Troubleshooting\n\n### Cache Not Being Generated\n\n1. Ensure `STATIC_ENABLED=true` is set in your `.env`\n2. Verify the `StaticResponse` middleware is applied to your routes\n3. Check that the storage disk is writable\n4. Routes with parameters cannot be cached automatically\n5. Only `200 OK` responses are cached\n\n### Cache Not Being Served\n\n1. Verify static files exist in your storage directory\n2. Check web server configuration\n3. Ensure the bypass header is not being sent accidentally\n\n### Crawler Not Finding Pages\n\n1. Check if pages are linked from the homepage\n2. Verify `accept_no_follow` setting if using `rel=\"nofollow\"` links\n3. Review your crawl profile configuration\n4. Note: JavaScript-rendered content is not supported\n\n### File Path Too Long\n\nIf you encounter file path length errors:\n\n1. Check the `filepath_max_length` and `filename_max_length` settings\n2. Consider using shorter URLs or disabling query string caching\n3. The package will skip files that exceed the configured limits\n\n## Testing\n\nRun the test suite:\n\n```bash\ncomposer test\n```\n\nRun tests with coverage:\n\n```bash\ncomposer test-coverage\n```\n\nRun static analysis:\n\n```bash\ncomposer analyse\n```\n\nFormat code:\n\n```bash\ncomposer format\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n\n- [Mark van Eijk](https://github.com/backstagephp)\n- [All Contributors](../../contributors)\n\nBuilt with [Spatie Crawler](https://github.com/spatie/crawler) and [voku/HtmlMin](https://github.com/voku/HtmlMin).\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbackstagephp%2Flaravel-static","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbackstagephp%2Flaravel-static","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbackstagephp%2Flaravel-static/lists"}