Encited logo
Pricing

How to Integrate Pre-rendering with Laravel

Integrate pre-rendering with Laravel middleware and serve rendered HTML to eligible crawlers before route middleware handles the request.

How Laravel pre-rendering works

Laravel can prepend Encited to its middleware pipeline so crawler requests receive rendered HTML before route middleware and controllers execute.

The Laravel middleware calls Encited only for eligible crawler HTML GET requests. Visitors and failed render calls continue through the normal request handler.

Prerequisites

  • A Laravel application with access to bootstrap/app.php middleware configuration.
  • The built-in Laravel HTTP client available in the deployed application.
  • LOVABLEHTML_API_KEY set in the production environment.

Set up pre-rendering with Laravel

1

Create the Laravel middleware

Save the class at app/Http/Middleware/EncitedPrerender.php. It uses Laravel's built-in HTTP client.

2

Register it before route middleware

Prepend EncitedPrerender in bootstrap/app.php so public crawler GET requests are intercepted first.

3

Deploy Laravel

Set LOVABLEHTML_API_KEY in the production environment, clear cached configuration, and deploy.

php artisan config:clear
EncitedPrerender.php
CopyDownload
<?php
// app/Http/Middleware/EncitedPrerender.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class EncitedPrerender
{
private const CRAWLER = '/(?:(OAI-SearchBot|ChatGPT-User(?:\/\d+\.\d+)?|GPTBot|ChatGPT|OpenAI-))|(?:(anthropic-ai|ClaudeBot|claude-web|Claude-Web|Claude-User|Claude-SearchBot))|(?:(GrokBot|xAI-Grok))|(?:(PerplexityBot|Perplexity-User))|(?:(Google-InspectionTool|GoogleOther(?:-Image|-Video)?|Googlebot|Google-CloudVertexBot|Storebot-Google))|(?:(Microsoft-Copilot|CopilotBot|Copilot-Chat|Microsoft-CopilotPlugin))|(?:(BingBot|bingbot|BingPreview))|(?:facebookexternalhit.*Twitterbot|Twitterbot.*facebookexternalhit)|(?:(Meta-ExternalAgent|meta-externalagent|meta-externalfetcher|MetaBot|FacebookBot|facebookexternalhit))|(?:LinkedInBot)|(?:Amazonbot)|(?:(Applebot-Extended|Applebot))|(?:Bytespider)|(?:(DuckAssistBot|DuckDuckBot))|(?:(cohere-training-data-crawler|cohere-ai|CohereAI))|(?:MistralAI-User)|(?:AI2Bot)|(?:CCBot)|(?:Diffbot)|(?:omgili)|(?:TimpiBot)|(?:YouBot)|(?:(Bravebot|Brave-Search))|(?:Yandex)|(?:Twitterbot)|(?:Discordbot)|(?:(Slackbot|Slack-ImgProxy))|(?:TelegramBot)|(?:(Pinterest|Pinterestbot))|(?:Snapchat)|(?:Redditbot)|(?:Tumblr)|(?:(Mastodon|http\.rb))|(?:Viber)|(?:^Line\/)|(?:WhatsApp)|(?:(SkypeUriPreview|Skype))|(?:(Teams|MicrosoftPreview))|(?:Zoom)|(?:Notion)|(?:(Quora|QuoraBot|Quora-Bot|Poe-Bot))|(?:Medium)|(?:(Pocket|PocketParser|PocketImageCache))|(?:(Flipboard|FlipboardProxy))|(?:(Embedly|embed\.ly))|(?:(Baiduspider|Baidu))|(?:(Sogou|sogou))|(?:(Yeti|NaverBot|Naver))|(?:SeznamBot)|(?:(Qwantify|Qwant))|(?:Ecosia)|(?:MojeekBot)|(?:Mail\.RU_Bot)|(?:Yahoo! Slurp)|(?:(SemrushBot|SEMrush))|(?:(AhrefsBot|AhrefsSiteAudit))|(?:(DotBot|Rogerbot|moz\.com))|(?:MJ12bot)|(?:Screaming Frog)|(?:SISTRIX)|(?:SEOkicks)|(?:serpstatbot)|(?:RSiteAuditor)|(?:LvHTML-SEOAuditBot)|(?:Barkrowler)|(?:HubSpot Crawler)|(?:(AwarioSmartBot|AwarioBot))/i';
private const FORWARD_HEADERS = [
'accept-language', 'sec-fetch-mode', 'sec-fetch-site', 'sec-fetch-dest',
'sec-fetch-user', 'upgrade-insecure-requests', 'referer', 'user-agent',
];
private const RESPONSE_HEADERS = [
'x-lovablehtml-render-cache', 'x-lovablehtml-snapshot-key', 'cache-control',
'etag', 'content-digest', 'signature-input', 'signature',
];
public function handle(Request $request, Closure $next)
{
$accept = $request->header('accept', '');
$userAgent = $request->userAgent() ?? '';
$isHtml = $accept === '' || $accept === '*/*' || str_contains($accept, 'text/html');
if (!$request->isMethod('GET') || !$isHtml || !preg_match(self::CRAWLER, $userAgent)) {
return $next($request);
}
try {
$headers = [
'x-lovablehtml-api-key' => (string) getenv('LOVABLEHTML_API_KEY'),
'accept' => 'text/html',
];
foreach (self::FORWARD_HEADERS as $name) {
$value = $request->header($name);
if ($value) $headers[$name] = $value;
}
$rendered = Http::timeout(30)
->connectTimeout(2)
->withoutRedirecting()
->withHeaders($headers)
->get('https://encited.com/api/prerender/render', [
'url' => $request->fullUrl(),
]);
if ($rendered->status() === 301 && $rendered->header('location')) {
return redirect()->away($rendered->header('location'), 301);
}
if ($rendered->status() === 304) return $next($request);
$contentType = $rendered->header('content-type') ?? '';
if ($rendered->status() === 200 && str_contains($contentType, 'text/html')) {
$responseHeaders = ['content-type' => 'text/html; charset=utf-8'];
foreach (self::RESPONSE_HEADERS as $name) {
$value = $rendered->header($name);
if ($value) $responseHeaders[$name] = $value;
}
return response($rendered->body(), 200, $responseHeaders);
}
} catch (\Throwable $error) {
// Fail open.
}
return $next($request);
}
}
// Register before route/auth middleware in bootstrap/app.php.

Test the integration

1. Send a crawler request

curl -sS -D - -o /dev/null \ -A "Googlebot" \ -H "Accept: text/html" \ "https://your-domain.com/"

Look for a successful HTML response and the x-lovablehtml-render-cache header.

2. Confirm browser passthrough

curl -sS -D - -o /dev/null \ -A "Mozilla/5.0" \ -H "Accept: text/html" \ -H "Accept-Language: en-US" \ -H "Sec-Fetch-Mode: navigate" \ -H "Sec-Fetch-Dest: document" \ -H "Upgrade-Insecure-Requests: 1" \ "https://your-domain.com/"

This request should be served by your existing website without an Encited render-cache header.

Common errors

Laravel still uses an old or missing API key

Update the production environment, run php artisan config:clear, and restart long-running queue or application workers.

Crawler requests are redirected by route middleware

Prepend EncitedPrerender in bootstrap/app.php so it runs before authentication and route middleware.

Request and security behavior

  • Only eligible GET requests for public HTML pages are considered for pre-rendering.
  • Along with the public page URL, only the API key and crawler-classification headers are sent to Encited.
  • Cookie and Authorization headers are never forwarded to Encited.
  • If Encited is unavailable or does not return rendered HTML, the request falls through to your website.

Common questions

How does pre-rendering work with Laravel middleware?

Laravel middleware identifies eligible crawler requests, returns rendered HTML from Encited, and passes normal browser traffic to the application.

Where should the Laravel pre-rendering middleware be registered?

Prepend it in bootstrap/app.php so it executes before authentication, redirects, and route-specific middleware.

How do I verify Laravel crawler pre-rendering?

Send a Googlebot HTML request to a public route and confirm that the response includes x-lovablehtml-render-cache.

Avatar
How can we help?
Get instant answers to your questions or leave a message for an engineer will reach out
Ask AI about Encited
See our docs
Contact support
Leave a message
We'll get back to you soon
Avatar
Ask AI about Encited
Team is also here to help
Thinking
Preview
Drop an image to attach
Powered by ReplyMaven