Skip to content

Instantly share code, notes, and snippets.

@sitefinitySDK
Created May 26, 2026 08:15
Show Gist options
  • Select an option

  • Save sitefinitySDK/7f55d4a3d2e38652254b8c9fc3059b64 to your computer and use it in GitHub Desktop.

Select an option

Save sitefinitySDK/7f55d4a3d2e38652254b8c9fc3059b64 to your computer and use it in GitHub Desktop.
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment