Automatic detection of externally rendered pages
Enable automatic detection of externally rendered pages
To enable automatic detection, perform the following:
- Update your proxy file using the sample below.
Depending on your project structure, this file may be named
proxy.tsormiddleware.ts. - In your environment configuration, set the following variable:
SF_PROXY_BY_DEFAULT="true" - Restart the Next.js renderer application.
When SF_PROXY_BY_DEFAULT is set to true, the proxy switches to proxy-by-default mode. In this mode, requests are sent to Sitefinity unless the middleware detects that the requested page is externally rendered and should be served through Next.js.
When to use fallback path settings
Automatic detection does not replace all proxy-related configuration.
Use the following settings when you need explicit routing behavior:
- Use
SF_WHITELISTED_PATHSfor legacy MVC pages, WebForms pages, or custom paths that must always be proxied to Sitefinity. - Use
SF_IS_HOME_PAGE_LEGACY="true"when the site home page (/) is still implemented as a legacy page and must be proxied to Sitefinity. - Use
SF_WHITELISTED_WEBSERVICESfor additional Sitefinity web service endpoints that must pass through the proxy.
If your project still depends on explicit frontend path routing, SF_WHITELISTED_NEXTJS_PATHS remains a valid fallback option.
Proxy sample
TypeScript
import { NextRequest, NextResponse } from 'next/server';
import { RootUrlService, RENDERER_NAME } from '@progress/sitefinity-nextjs-sdk/rest-sdk';
const headerBypassHostValidationKey = 'X-SF-BYPASS-HOST-VALIDATION-KEY';
const headerBypassHostKey = 'X-SF-BYPASS-HOST';
const whitelistedServices: string[] = [];
if (process.env.SF_WHITELISTED_WEBSERVICES) {
whitelistedServices.push(...process.env.SF_WHITELISTED_WEBSERVICES.split(',').map(x => x.trim()[0] === '/' ? x.trim() : `/${x.trim()}`));
}
const whitelistedNextJsPagePaths: string[] = [];
if (process.env.SF_WHITELISTED_NEXTJS_PATHS) {
whitelistedNextJsPagePaths.push(...process.env.SF_WHITELISTED_NEXTJS_PATHS.split(',').map(x => x.trim()[0] === '/' ? x.trim() : `/${x.trim()}`));
}
const servicePath = RootUrlService.getWebServicePath();
export async function proxy(request: NextRequest) {
const resultFrontend = await middlewareFrontend(request);
if (resultFrontend instanceof Response) {
return resultFrontend;
} else if (resultFrontend instanceof NextRequest) {
request = resultFrontend;
}
const resultBackend = await middlewareBackend(request);
if (resultBackend instanceof Response) {
return resultBackend;
}
if (whitelistedNextJsPagePaths && whitelistedNextJsPagePaths.length > 0) {
// handles edit and preview cases
if (request.nextUrl.searchParams.has('sfaction')) {
return NextResponse.next();
}
// handles frontend whitelisting of pages/
for (let i = 0; i < whitelistedNextJsPagePaths.length; i++) {
const path = whitelistedNextJsPagePaths[i];
if (request.nextUrl.pathname === path) {
return NextResponse.next();
}
}
let bypassHost = shouldBypassHost(request);
return rewriteSystemRequest(request, bypassHost, false);
}
// default behavior to render pages
return NextResponse.next();
}
async function middlewareFrontend(request: NextRequest) {
// handle known paths
if (request.nextUrl.pathname.startsWith('/assets') ||
request.nextUrl.pathname.startsWith('/_next') ||
request.nextUrl.pathname === '/favicon.ico') {
return NextResponse.next();
}
if (request.nextUrl.pathname === '/sfrenderer/api/v1/health/status') {
return new NextResponse(undefined, { status: 200 });
}
let bypassHost = shouldBypassHost(request);
// user defined paths that can be additionally proxied
// can be used for legacy MVC/WebForms pages or paths that are entirely custom
const whitelistedPaths: string[] = [];
if (process.env.SF_WHITELISTED_PATHS) {
const whiteListedPathsFromEnvironment = (process.env.SF_WHITELISTED_PATHS as string).split(',').map(x => x.trim()[0] === '/' ? x.trim() : `/${x.trim()}`);
whitelistedPaths.push(...whiteListedPathsFromEnvironment);
}
//handle known CMS paths
const cmsPaths = [
`/${servicePath}`,
'/forms/submit',
'/sitefinity/anticsrf',
'/sitefinity/login-handler',
'/sitefinity/signout/selflog',
'/ResourcePackages',
'/web-interface/calendars',
'/web-interface/events',
'/kendo',
...whitelistedServices,
...whitelistedPaths
];
if (bypassHost ||
cmsPaths.some(path => request.nextUrl.pathname.toUpperCase().startsWith(path.toUpperCase())) ||
request.nextUrl.pathname.indexOf('.axd') !== -1 ||
isAppStatusRequest(request) ||
proxyHomePage(request)) {
return rewriteSystemRequest(request, bypassHost);
}
return request;
}
async function middlewareBackend(request: NextRequest) {
const bypassHost = shouldBypassHost(request);
//handle known CMS paths
const cmsPaths = [
servicePath,
...whitelistedServices
];
cmsPaths.push(...[
'/sf/',
'/Sitefinity/Services',
'/Sitefinity/adminapp',
'/Sitefinity/SignOut',
'/SFSitemap/',
'/adminapp',
'/sf/system',
'/ws/',
'/restapi/',
'/contextual-help',
'/res/',
'/admin-bridge/',
'/sfres/',
'/images/',
'/documents/',
'/docs/',
'/videos/',
'/forms/submit',
'/ExtRes/',
'/TranslationRes/',
'/RBinRes/',
'/ABTestingRes/',
'/DataIntelligenceConnector/',
'/signin-facebook',
'/signin-google',
'/signin-microsoft',
'/signin-twitter',
'/Frontend-Assembly/',
'/Telerik.Sitefinity.Frontend/'
]);
if (bypassHost ||
request.nextUrl.pathname.indexOf('.axd') !== -1 ||
request.nextUrl.pathname.indexOf('.ashx') !== -1 ||
cmsPaths.some(path => request.nextUrl.pathname.toUpperCase().startsWith(path.toUpperCase())) ||
request.nextUrl.pathname.toLowerCase() === '/sitefinity' ||
/\/sitefinity\/(?!(template|forms))/i.test(request.nextUrl.pathname)) {
return rewriteSystemRequest(request, bypassHost);
}
return request;
}
async function rewriteSystemRequest(request: NextRequest, bypassHost: string, sendRendererProxyHeaders: boolean = true) {
const { url, headers } = generateProxyRequest(request, bypassHost, sendRendererProxyHeaders);
const response = NextResponse.rewrite(url, {
request: {
headers: headers
}
});
// nextjs issue - overriding of proxied headers is not working
// https://github.com/vercel/next.js/issues/70515
if (bypassHost) {
response.headers.set('sf-cache-control-override', 'no-cache');
}
return response;
}
function shouldBypassHost(request: NextRequest) {
let bypassHost = '';
const remoteValidationKey = process.env.SF_REMOTE_VALIDATION_KEY;
if (remoteValidationKey) {
if (request.headers.has(headerBypassHostKey) && request.headers.has(headerBypassHostValidationKey)) {
const bypassHostKey = request.headers.get(headerBypassHostValidationKey);
const bypassHostValue = request.headers.get(headerBypassHostKey);
if (bypassHostKey && bypassHostValue && bypassHostKey === remoteValidationKey) {
bypassHost = bypassHostValue;
} else {
throw new Error(`The provided local validation key - '${remoteValidationKey}' is not valid or it is expired.`);
}
}
}
return bypassHost;
}
function generateProxyRequest(request: NextRequest, bypassHost: string, sendRendererProxyHeaders: boolean = true) {
const headers = new Headers(request.headers);
if (sendRendererProxyHeaders) {
headers.set('X-SFRENDERER-PROXY', 'true');
headers.set('X-SFRENDERER-PROXY-NAME', RENDERER_NAME);
if (!headers.has('X-SF-WEBSERVICEPATH')) {
headers.set('X-SF-WEBSERVICEPATH', RootUrlService.getWebServicePath());
}
}
if (!headers.has('x-sf-correlation-id')) {
headers.set('x-sf-correlation-id', generateRandomString());
}
let resolvedHost = process.env.SF_PROXY_ORIGINAL_HOST || request.headers.get('X-FORWARDED-HOST') || request.nextUrl.host;
if (!resolvedHost) {
if (process.env.PORT) {
resolvedHost = `localhost:${process.env.PORT}`;
} else {
resolvedHost = 'localhost';
}
}
const hostHeaderName = process.env.SF_HOST_HEADER_NAME || 'X-ORIGINAL-HOST';
if (process.env.SF_LOCAL_VALIDATION_KEY || process.env.SF_REMOTE_VALIDATION_KEY) {
headers.delete(hostHeaderName);
if (process.env.SF_LOCAL_VALIDATION_KEY) {
headers.set(headerBypassHostKey, resolvedHost);
headers.set(headerBypassHostValidationKey, process.env.SF_LOCAL_VALIDATION_KEY);
} else if (bypassHost) {
headers.set(hostHeaderName, bypassHost);
} else {
headers.set(hostHeaderName, resolvedHost);
}
} else {
headers.set(hostHeaderName, resolvedHost);
}
const proxyURL = new URL(process.env.SF_CMS_URL!);
const url = new URL(request.url);
headers.set('HOST', proxyURL.hostname);
url.hostname = proxyURL.hostname;
url.protocol = proxyURL.protocol;
url.port = proxyURL.port;
return { url, headers };
}
function isAppStatusRequest(request: NextRequest) {
return request.nextUrl.pathname.toLowerCase() === '/appstatus' &&
request.headers.get('accept')?.indexOf('application/json') !== -1;
}
function proxyHomePage(request: NextRequest) {
// if home page is made with a renderer, it will be handled by the home page logic here in nextjs
// if it is legacy page (MVC, Web form), proxy the request to Sitefinity
const isLegacyHomePage: string = process.env.SF_IS_HOME_PAGE_LEGACY || 'false';
return request.nextUrl.pathname === '/' && isLegacyHomePage.toLocaleLowerCase() === 'true';
}
function generateRandomString() {
let result = '';
let length = 16;
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for ( let i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
Want to learn more?
Enhance your Sitefinity skills by enrolling in free training sessions. Become Sitefinity certified through Progress Education Community to strengthen your professional credentials.
Get started with Integration Hub | Sitefinity Cloud
This free lesson teaches administrators, marketers, and other business professionals how to use Sitefinity Integration Hub to create automated workflows between Sitefinity and other business systems.
Web Security for Sitefinity Administrators
This free lesson teaches administrators the basics about protecting your Sitefinity instance and your sites from external threats. Configure HTTPS, SSL, allow lists for trusted sites, and cookie security, among others.
Foundations of Sitefinity ASP.NET Core Development
The free on-demand video course teaches developers how to use Sitefinity ASP.NET Core and take advantage of its decoupled architecture and modern development model.