Encited logo
Pricing

How to Integrate Pre-rendering with Django

Integrate pre-rendering with Django middleware so public crawler GET requests receive rendered HTML before views and authentication run.

How Django pre-rendering works

Django can place Encited first in its middleware chain so crawler HTML requests are rendered before URL routing, views, and authentication.

Eligible crawler GET requests are handled before the Django view. Browser traffic and render failures call the next middleware and continue normally.

Prerequisites

  • A Django deployment that can make outbound HTTPS requests.
  • The requests package installed in the production environment.
  • LOVABLEHTML_API_KEY configured for Gunicorn, uWSGI, Docker, or the hosting service.

Set up pre-rendering with Django

1

Install the HTTP client

The middleware uses requests for the outbound render call.

pip install requests
2

Add the middleware first

Save the snippet in your project and put EncitedPrerenderMiddleware at the top of settings.py MIDDLEWARE.

3

Restart Django

Set LOVABLEHTML_API_KEY and restart Gunicorn, uWSGI, Docker, or your hosting deployment.

encited_prerender.py
CopyDownload
# myproject/middleware/encited_prerender.py
import re
import os
from urllib.parse import quote
import requests
from django.http import HttpResponse, HttpResponsePermanentRedirect
CRAWLER = re.compile(r"(?:(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))", re.IGNORECASE)
FORWARD_HEADERS = (
"accept-language", "sec-fetch-mode", "sec-fetch-site", "sec-fetch-dest",
"sec-fetch-user", "upgrade-insecure-requests", "referer", "user-agent",
)
RESPONSE_HEADERS = (
"x-lovablehtml-render-cache", "x-lovablehtml-snapshot-key", "cache-control",
"etag", "content-digest", "signature-input", "signature",
)
class EncitedPrerenderMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
accept = request.headers.get("accept", "")
user_agent = request.headers.get("user-agent", "")
api_key = os.environ.get("LOVABLEHTML_API_KEY")
is_html = not accept or accept == "*/*" or "text/html" in accept
if not api_key or request.method != "GET" or not is_html or not CRAWLER.search(user_agent):
return self.get_response(request)
endpoint = (
"https://encited.com/api/prerender/render?url="
+ quote(request.build_absolute_uri(), safe="")
)
try:
headers = {
"x-lovablehtml-api-key": api_key,
"accept": "text/html",
}
for name in FORWARD_HEADERS:
value = request.headers.get(name)
if value:
headers[name] = value
rendered = requests.get(
endpoint,
headers=headers,
timeout=(2, 30),
allow_redirects=False,
)
if rendered.status_code == 301 and rendered.headers.get("location"):
return HttpResponsePermanentRedirect(rendered.headers["location"])
if rendered.status_code == 304:
return self.get_response(request)
if rendered.status_code == 200 and "text/html" in rendered.headers.get("content-type", ""):
response = HttpResponse(
rendered.content,
status=200,
content_type="text/html; charset=utf-8",
)
for name in RESPONSE_HEADERS:
value = rendered.headers.get(name)
if value:
response[name] = value
return response
except requests.RequestException:
pass
return self.get_response(request)
# settings.py - keep this first in MIDDLEWARE
# MIDDLEWARE = ["myproject.middleware.encited_prerender.EncitedPrerenderMiddleware", ...]

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

Django raises ModuleNotFoundError for requests

Install requests in the production dependency set and rebuild the same environment that runs Django.

The middleware never sees crawler requests

Place EncitedPrerenderMiddleware at the top of settings.py MIDDLEWARE and restart every application worker.

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 Django middleware?

The first Django middleware checks crawler HTML requests, serves Encited rendered HTML, and delegates ordinary traffic to the remaining middleware chain.

Where should pre-rendering appear in Django MIDDLEWARE?

Place it first so sessions, authentication, redirects, and view middleware cannot handle eligible crawler requests before Encited.

Will Django admin or signed-in users reach Encited?

Normal browser requests continue to Django, and the integration does not forward Cookie or Authorization headers to Encited.

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