codebar-ag/laravel-prerender
Laravel Prerender adds middleware that detects search engine and social crawlers and serves them prerendered HTML, helping JavaScript-heavy SPAs and dynamic pages get indexed. Configurable crawler rules, excluded paths, and Prerender.io integration make SEO setup simple.
This is a thin, single-purpose package: it detects crawler/bot user agents, forwards the request URL to a prerender service (prerender.io by default, or a self-hosted instance), and returns the rendered HTML instead of letting the SPA boot in the visitor's browser. It is archived and last released 2025-03-11, so treat it as a stable snapshot you may need to fork — pin the version and be ready to vendor the middleware class into app/Http/Middleware if you need patches.
Minimal setup:
composer require codebar-ag/laravel-prerender
php artisan vendor:publish --tag="laravel-prerender-config"
Set the token you get from prerender.io in .env:
PRERENDER_ENABLE=true
PRERENDER_TOKEN=your-service-token
PRERENDER_URL=https://service.prerender.io/
Register the middleware. Laravel 11+ (bootstrap/app.php):
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\CodebarAg\LaravelPrerender\Http\Middleware\PrerenderMiddleware::class,
]);
})
Laravel 10 and earlier (app/Http/Kernel.php):
protected $middlewareGroups = [
'web' => [
// ...
\CodebarAg\LaravelPrerender\Http\Middleware\PrerenderMiddleware::class,
],
];
Where to look first: open config/prerender.php (bot user-agent list, token, URL, ignore/whitelist rules) and the middleware class itself in src/. The middleware is short — reading it once tells you exactly which request conditions trigger a prerender, which is the source of most surprises.
First use case: an SPA or Vue/React-rendered marketing site where Googlebot, LinkedIn, Slack, and Discord need real HTML for previews and indexing. Verify immediately with a spoofed user agent:
curl -A "Googlebot/2.1 (+http://www.google.com/bot.html)" -I https://example.com/pricing
You should see the prerendered response (prerender.io sets an X-Prerender header). Normal browser requests should be untouched.
Short-circuit at the top of the web group. The middleware typically intercepts bots and returns the prerendered response without invoking your controller, so put it early in the group to skip session, auth, and DB work for crawler traffic. If you need controller-produced responses (e.g. correct status codes from your app), place it later — but then you pay the full framework cost per bot hit.
Scope it to public, indexable routes. Crawlers hitting /admin, /api, or /checkout are either malicious or pointless. Use the config ignore_urls / blacklist patterns, or register the middleware per-route:
Route::middleware('prerender')->group(function () {
Route::get('/', HomeController::class);
Route::get('/pricing', PricingController::class);
Route::get('/blog/{slug}', BlogController::class);
});
Self-hosted prerender via Docker. Point PRERENDER_URL at an internal Chrome-based renderer (http://prerender:3000/) instead of the SaaS. This removes per-request cost and keeps content on your network, but you now own the Chrome container, memory limits, and version drift. In this mode PRERENDER_TOKEN is usually blank.
Env-gate the middleware. Disable in local and testing so unit tests don't make outbound HTTP calls:
PRERENDER_ENABLE=false
Then write one dedicated test that flips it on and asserts the bot path, and let everything else run clean.
Layer your own cache in front. prerender.io caches rendered pages server-side, but every cache miss still round-trips to your origin. For high-traffic marketing pages, put a response cache (or CDN) in front keyed on URL, and purge it on deploy. The prerender.io API also lets you purge individual URLs when content changes — worth wiring into your publish/deploy hooks.
Test with real bot user agents in CI. One feature test is enough:
$this->withHeader('User-Agent', 'Googlebot/2.1 (+http://www.google.com/bot.html)')
->get('/pricing')
->assertSee('Pricing'); // text that only exists after JS runs
Keep a second test asserting a normal browser UA is not prerendered, so a config regression doesn't silently double-serve your site.
Social preview debugging is the fastest win. Slack, Discord, LinkedIn, and Twitter/X all fetch with their own UAs and do not execute JS. After enabling, paste a URL into each platform's preview debugger — if the card renders, the pipeline works.
Archived = no upstream fixes. Bot UA lists, TLS behavior, and Laravel 12 middleware APIs will drift. Fork the repo into your org or copy the middleware into app/Http/Middleware on day one. Prefer a small in-house class over an abandoned dependency for anything in the request path.
Only GET requests are safe. The middleware should skip non-GET methods, requests with _escaped_fragment_, AJAX requests (X-Requested-With), and any request already carrying X-Prerender. If it doesn't, you can accidentally replay a POST to the rendering service. Read the middleware source and confirm this filter exists — if not, add it.
Middleware order matters more than you expect. Registered globally, it intercepts /api/* and health checks. Registered at the bottom of web, it inherits session/cookie middleware you may not want a remote renderer to see. Put it immediately after TrustProxies/HandleCors and before anything that writes state.
Trusted proxies and X-Forwarded-*. Behind a load balancer, the middleware must build the absolute URL from the correct host and scheme. Ensure TrustProxies is configured, otherwise the prerender service is asked to fetch http://internal-ip/... and you get blank pages or 502s.
Bot user-agent lists rot. Googlebot now renders JS fine; the real beneficiaries are Bing, social scrapers, and older crawlers. If you're only chasing Google SEO, you may not need this at all. Serving different HTML to bots is technically cloaking — it's tolerated because the HTML matches what JS produces, but only if it genuinely matches. Never serve bot-only promos or hidden links.
Status codes can get lost. Some prerender setups return 200 for pages your app would 404. The middleware may not forward the upstream status. If SEO correctness matters for missing pages, verify with curl -A "Googlebot..." -I /nonexistent and patch the response handling if needed.
Response headers get overwritten. The prerendered response carries its own Content-Type, Cache-Control, and X-Prerender. If you set Vary: User-Agent downstream, you'll defeat your CDN caching. Decide deliberately: either Vary: User-Agent (correct, slower) or separate cache keys per UA class (faster, needs care).
Token leakage. PRERENDER_TOKEN in query strings shows up in access logs and error trackers. Prefer the self-hosted mode for anything sensitive, or scrub the token from log formatters.
Local debugging checklist: set PRERENDER_ENABLE=true, PRERENDER_URL to the SaaS, curl with a known bot UA, and watch storage/logs/laravel.log and the prerender.io dashboard's request log simultaneously — the dashboard shows whether your request arrived and whether their renderer succeeded, which instantly separates "middleware didn't fire" from "renderer failed."
Extension point: the middleware is the only real seam. If you need per-route config, conditional bypasses (e.g. skip when a feature flag disables SSR), or a different rendering provider, subclass it and override the UA check and the HTTP call rather than fighting the config file.
How can I help you explore Laravel packages today?