Encited logo
Pricing

How to Integrate Pre-rendering with ASP.NET Core

Integrate pre-rendering with ASP.NET Core middleware before endpoint mapping so eligible crawlers receive rendered HTML.

How ASP.NET pre-rendering works

ASP.NET Core can add Encited before endpoint mapping and use a named HTTP client that preserves redirect responses from the render API.

The middleware handles eligible crawler HTML GET requests before endpoints. Browser traffic and non-render responses continue through the ASP.NET Core pipeline.

Prerequisites

  • An ASP.NET Core application with access to Program.cs service and middleware registration.
  • A named HttpClient configured with automatic redirects disabled.
  • LOVABLEHTML_API_KEY configured in the deployment environment.

Set up pre-rendering with ASP.NET

1

Add the ASP.NET middleware

Save EncitedPrerenderMiddleware.cs in the web project and keep it before endpoint mapping.

2

Register HTTP and middleware services

Copy both Program.cs lines from the snippet. The named HTTP client disables automatic redirects so Encited redirect rules reach the crawler, and the middleware must run before MapControllers or MapFallback.

3

Publish and deploy

Provide LOVABLEHTML_API_KEY through your host and publish the updated application.

dotnet publish -c Release
EncitedPrerenderMiddleware.cs
CopyDownload
// EncitedPrerenderMiddleware.cs
using System.Text.RegularExpressions;
public sealed class EncitedPrerenderMiddleware
{
private static readonly Regex Crawler = new(
"(?:(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))",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly string[] ForwardHeaders = new[] {
"accept-language", "sec-fetch-mode", "sec-fetch-site", "sec-fetch-dest",
"sec-fetch-user", "upgrade-insecure-requests", "referer", "user-agent"
};
private static readonly string[] ResponseHeaders = new[] {
"x-lovablehtml-render-cache", "x-lovablehtml-snapshot-key", "cache-control",
"etag", "content-digest", "signature-input", "signature"
};
private readonly RequestDelegate _next;
private readonly HttpClient _http;
public EncitedPrerenderMiddleware(RequestDelegate next, IHttpClientFactory clients)
{
_next = next;
_http = clients.CreateClient("Encited");
}
public async Task InvokeAsync(HttpContext context)
{
var request = context.Request;
var accept = request.Headers.Accept.ToString();
var userAgent = request.Headers.UserAgent.ToString();
var isHtml = string.IsNullOrEmpty(accept) || accept == "*/*" || accept.Contains("text/html");
if (!HttpMethods.IsGet(request.Method) || !isHtml || !Crawler.IsMatch(userAgent))
{
await _next(context);
return;
}
var publicUrl = $"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}{request.QueryString}";
var endpoint = "https://encited.com/api/prerender/render?url=" + Uri.EscapeDataString(publicUrl);
try
{
using var renderedRequest = new HttpRequestMessage(HttpMethod.Get, endpoint);
renderedRequest.Headers.TryAddWithoutValidation(
"x-lovablehtml-api-key",
Environment.GetEnvironmentVariable("LOVABLEHTML_API_KEY")
?? throw new InvalidOperationException("LOVABLEHTML_API_KEY is not set"));
renderedRequest.Headers.TryAddWithoutValidation("accept", "text/html");
foreach (var name in ForwardHeaders)
{
var value = request.Headers[name].ToString();
if (!string.IsNullOrEmpty(value)) renderedRequest.Headers.TryAddWithoutValidation(name, value);
}
using var rendered = await _http.SendAsync(renderedRequest);
if ((int)rendered.StatusCode == 301 && rendered.Headers.Location is not null)
{
context.Response.StatusCode = 301;
context.Response.Headers.Location = rendered.Headers.Location.ToString();
return;
}
if ((int)rendered.StatusCode == 200
&& rendered.Content.Headers.ContentType?.MediaType == "text/html")
{
context.Response.StatusCode = 200;
context.Response.ContentType = "text/html; charset=utf-8";
foreach (var name in ResponseHeaders)
{
if (rendered.Headers.TryGetValues(name, out var values))
context.Response.Headers[name] = values.ToArray();
else if (rendered.Content.Headers.TryGetValues(name, out values))
context.Response.Headers[name] = values.ToArray();
}
await context.Response.Body.WriteAsync(await rendered.Content.ReadAsByteArrayAsync());
return;
}
}
catch
{
// Fail open.
}
await _next(context);
}
}
// Program.cs
// builder.Services.AddHttpClient("Encited").ConfigurePrimaryHttpMessageHandler(
// () => new HttpClientHandler { AllowAutoRedirect = false });
// app.UseMiddleware<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

Encited redirect rules disappear inside HttpClient

Use the named EncitedPrerender client with AllowAutoRedirect set to false so the crawler receives the intended redirect.

MapFallback handles crawler requests first

Register UseMiddleware<EncitedPrerenderMiddleware>() before MapControllers, MapFallback, and other endpoint mapping calls.

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 ASP.NET Core middleware?

The middleware calls Encited for eligible crawlers before endpoint mapping and invokes the next delegate for ordinary browser traffic.

Where should ASP.NET Core pre-rendering middleware run?

Place it before MapControllers, MapFallback, authentication-dependent endpoints, and the SPA fallback.

Why disable automatic redirects in the ASP.NET HTTP client?

It allows configured Encited 301 responses to reach the crawler instead of being followed internally by HttpClient.

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