setup admin panel proxy
Attaching the admin panel on your website is an easy step
This ensures your editing experience is seamless. With the admin panel attached to your website via a proxy, you can edit your website on the same URL and have a visual preview of the changes in real time. It also enables overlay editing which is a better user experience in editing the webpage.
The proxy is a proxy.ts file in your project (in Next.js 16; on older Next.js it's middleware.ts). It behaves like a URL rewrite, resolving the /admin, /api, and /auth endpoints of your website to the CMS.
localhost: you must use a tunnel (since CMS cannot access localhost directly)// proxy.ts (Next.js 16 β use middleware.ts on older Next.js)
import { createCmsProxy, cmsProxyMatcher } from 'cms-renderer/lib/proxy';
import type { NextRequest } from 'next/server';
const cmsProxy = createCmsProxy({
// The CMS admin origin to proxy to.
upstream: process.env.ADMIN_UPSTREAM_ORIGIN ?? 'https://cms.dev.tryprofound.com',
});
export default async function proxy(request: NextRequest) {
return cmsProxy(request);
}
export const config = {
matcher: cmsProxyMatcher,
};
createCmsProxy returns a handler you call with the incoming request. cmsProxyMatcher is the ready-made matcher covering /admin, /api, /auth, and admin-originated /_next and static-asset requests β so you don't have to hand-write the matcher array.
On older Next.js versions, name the file
middleware.tsand export the handler asmiddlewareinstead of the default export:
export async function middleware(request: NextRequest) { return cmsProxy(request); } export const config = { matcher: cmsProxyMatcher };
Need to proxy extra paths? Pass
additionalPaths: ['/my-path']tocreateCmsProxy.
Now you can go to /admin on your site to access the admin panel. This enables live preview.
Overlays and edit-mode are rendered by your preview route, not the static production route. The admin loads your site in edit mode (?edit_mode=true) and that request is served by ParametricRoutePreviewPage on a force-dynamic route, which forwards searchParams for you:
// app/cms-preview_/[...slug]/page.tsx
import { ParametricRoutePreviewPage } from 'cms-renderer/lib/renderer';
import { registry } from '@/lib/registry';
import { cmsConfig } from '@/lib/cms-config';
// Dynamic rendering β allows searchParams for edit_mode
export const dynamic = 'force-dynamic';
interface PageProps {
params: Promise<{ slug: string[] }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
export default async function PreviewPage({ params, searchParams }: PageProps) {
const { slug } = await params;
return (
<ParametricRoutePreviewPage
registry={registry}
apiKey={cmsConfig.apiKey}
websiteId={cmsConfig.websiteId}
cmsUrl={cmsConfig.cmsUrl}
params={Promise.resolve({ slug })}
searchParams={searchParams}
/>
);
}
You don't wire overlays manually β passing searchParams into the preview renderer is all that's needed. Your production route (`app/[...slug]/page.tsx) stays static with ParametricRoutePage and needs no searchParams`. See Setup Hybrid CMS Project for the full two-route setup.
/admin on your websiteOverlays and live previews should work automatically.
Set up the full production + preview routes in Setup Hybrid CMS Project.