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

Prerender Bundle Laravel Package

yucca/prerender-bundle

Symfony bundle that detects crawler and bot user agents and serves prerendered HTML from Prerender.io, improving SEO and social link previews for JavaScript-heavy single-page apps while normal users continue to receive the standard response.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony2 bundle, not a Laravel package. For a Laravel codebase the practical move is to treat it as a reference implementation: the useful part is a small request listener that detects crawlers and proxies to prerender.io. Read it, then port the logic into a Laravel middleware rather than trying to install it.

  • Check composer.json first. It targets symfony/symfony ~2.x and PHP 5.x. On Laravel 10/11 (PHP 8.1+) it will not resolve; do not --ignore-platform-reqs it into production just to try it.
  • Legacy Symfony2 app only: composer require yucca/prerender-bundle, register the bundle in AppKernel.php, then add a yucca_prerender: block to config.yml with your prerender.io token (exact key names live in DependencyInjection/Configuration.php). Default service endpoint is https://service.prerender.io/.
  • Where to look first:
    1. README.md — supported config keys and the intended listener behavior.
    2. DependencyInjection/Configuration.php — every knob the bundle actually exposes.
    3. Resources/config/services.xml — listener registration and its kernel.request priority.
    4. The event listener class — crawler UA list, URL construction, and skip rules; this is what you reimplement in Laravel.
  • First use case: a JS-rendered marketing site or SPA where Googlebot sees an empty <div id="app">. Enable the listener (or your port), then compare:
    curl -s https://example.com/pricing | head        # normal user
    curl -s -A "Googlebot/2.1 (+http://www.google.com/bot.html)" https://example.com/pricing | head
    
    The second should return fully rendered HTML.

Implementation Patterns

The core pattern (port this to Laravel). Dynamic rendering proxy on kernel.request / HTTP middleware:

  1. Only GET (optionally HEAD).
  2. Match User-Agent against a crawler list, or detect ?_escaped_fragment_=.
  3. Skip asset extensions (.js, .css, .png, .json, …), excluded path prefixes, and XHR/JSON requests.
  4. Build https://service.prerender.io/{full_url} including scheme and host.
  5. Send X-Prerender-Token header, return the HTML and short-circuit the normal pipeline.

Laravel middleware sketch:

public function handle($request, Closure $next)
{
    if (! $request->isMethod('GET') || ! $this->isCrawler($request) || $this->isIgnored($request)) {
        return $next($request);
    }

    $url = $request->fullUrl(); // strip _escaped_fragment_ before sending

    try {
        $res = Http::withHeaders(['X-Prerender-Token' => config('services.prerender.token')])
            ->timeout(5)
            ->get('https://service.prerender.io/'.$url);

        if ($res->successful()) {
            return response($res->body(), $res->status())
                ->header('Content-Type', 'text/html; charset=UTF-8')
                ->header('Vary', 'User-Agent');
        }
    } catch (\Throwable $e) {
        report($e); // fall through to normal response, never 500 a bot
    }

    return $next($request);
}

Registration order matters. Put it in the global stack (or prepend to the web group) after TrustProxies so fullUrl() reflects the real scheme/host, but before session/auth-heavy middleware. Exclude api/*, health checks, and anything returning JSON.

Caching is the main cost control. Prerender charges per render; wrap the call:

$html = Cache::remember('prerender:'.sha1($url), now()->addHours(6), fn () => ...);

Invalidate per-URL in model observers on publish/update, or tag by content type. Also honor prerender's status-code response header convention (it often returns HTTP 200 and puts the real status in a header) so 404s are seen as 404s by crawlers.

Bot detection. Reuse prerender's recommended UA regex (Googlebot, bingbot, Yandex, Baiduspider, facebookexternalhit, Twitterbot, Slackbot, etc.) and support _escaped_fragment_ for the deprecated AJAX crawling scheme. Log every match at debug level — it makes "why is Google seeing this?" trivial to answer.

Self-hosted alternative. The same API contract is served by prerender/prerender in Docker; point your service URL at it. Put the service URL and token behind config so you can switch without touching middleware.

Symfony-side extension tips (if you actually maintain one). The listener is a normal service: override it to customize UA lists or the client, and raise its kernel.request priority if it is firing after routing/firewall. Because it returns a response from kernel.request, the controller, session, and auth never run — which is the point, but also means no user-specific content.

Gotchas and Tips

  • Abandoned since 2014. Symfony2 is EOL, there are zero dependents, and the score reflects that. Treat it as a pattern source, not a dependency. Budget for a port in any modern stack.
  • Cloaking risk. Serving rendered HTML to bots and something different to users is cloaking if content diverges. Dynamic rendering is tolerated by Google, but the rendered and user views must be materially equivalent — same canonical, same meta robots, same body content.
  • CDN cache poisoning. Without Vary: User-Agent (or a separate cache key for crawlers), a CDN can serve prerendered HTML to humans or the SPA shell to Googlebot. Set Vary and prefer short TTLs or private caching for prerendered responses.
  • Fail open, always. A prerender outage must fall back to $next($request). Set a 3–5s timeout; a bot waiting 30s on a hung proxy is worse than an unrendered page.
  • URL construction. Include scheme/host, drop fragments, convert _escaped_fragment_ back to the #! URL, and do not forward tracking query params you don't need — they create duplicate cache entries and duplicate renders.
  • Assets and JSON. Without extension and Accept-header filtering you will pay to render .js, .css, and API responses, which returns garbage and pollutes your cache.
  • Token handling. Keep PRERENDER_TOKEN in env/secrets, send it as a header only. Never put it in a URL or commit it to config.yml.
  • Status codes and redirects. Prerender may normalize redirects and status; inspect the response headers and map real statuses yourself, or 301/404 pages will look like 200s to crawlers.
  • Symfony listener priority quirk. If the bundle appears to do nothing, it's usually firing after the firewall or router. Check the tag priority in services.xml and bump it.
  • Debugging recipe. Reproduce directly outside the app first:
    curl -s -H "X-Prerender-Token: $TOKEN" \
      "https://service.prerender.io/https://example.com/pricing"
    
    If that's fine but the app isn't, the bug is in UA matching or skip rules, not prerender.
  • Extension points worth keeping in your port. Config-driven crawler list, ignore-path list, an injectable render client (swap SaaS for self-hosted), and a cache abstraction — those are the four seams you will actually need to customize.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core