Skip to content

Next.js Starter

The starter utilizes the App Router and React Server Components (RSC). Leveraging Next.js, data fetching happens server-side by default with force-cache, providing performance comparable to static sites.

1. Preview Implementation (app/api/preview/route.ts)

Section titled “1. Preview Implementation (app/api/preview/route.ts)”

Handles the handshake by verifying the signature via the backend API and sets a secure session cookie.

import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const path = searchParams.get('path') || '';
const expires = searchParams.get('expires');
const signature = searchParams.get('signature');
const internalUrl = process.env.INTERNAL_API_URL;
const verifyUrl = `${internalUrl}/api/preview/verify?path=${path}&expires=${expires}&signature=${signature}`;
try {
const response = await fetch(verifyUrl, { cache: 'no-store' });
const data = await response.json();
if (!response.ok || !data.valid) {
return new Response('Unauthorized signature', { status: 401 });
}
const cookieStore = await cookies();
cookieStore.set('pyxis_preview', data.preview_token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 3600
});
const redirectPath = (path === 'homepage' || path === '') ? '/' : `/${path}`;
redirect(redirectPath);
} catch (e) {
return new Response('API Connection Error: ' + e.message, { status: 500 });
}
}

2. Data Fetching (app/[[…slug]]/page.tsx)

Section titled “2. Data Fetching (app/[[…slug]]/page.tsx)”

The main page component that automatically forwards the preview header if an active session is detected.

async function getPageData(slugArray: string[] | undefined) {
const slug = slugArray?.length ? slugArray.join('/') : '';
const cookieStore = await cookies();
const previewToken = cookieStore.get('pyxis_preview')?.value;
const API_URL = process.env.INTERNAL_API_URL;
const headers: HeadersInit = { 'Accept': 'application/json' };
if (previewToken) headers['X-Pyxis-Preview'] = previewToken;
const res = await fetch(`${API_URL}/api/pages/${slug}`, {
cache: previewToken ? 'no-store' : 'force-cache',
headers: headers,
next: { revalidate: 3600 }
});
if (!res.ok) return null;
const json = await res.json();
return json.data;
}

3. Instant Revalidation (app/api/revalidate/route.ts)

Section titled “3. Instant Revalidation (app/api/revalidate/route.ts)”

A webhook endpoint that invalidates the Next.js cache immediately after CMS publication using revalidatePath.

import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { secret, path } = body;
// Verify token
if (secret !== process.env.REVALIDATE_TOKEN) {
console.warn(`[Revalidate] Unauthorized attempt with secret: ${secret}`);
return NextResponse.json({ message: 'Invalid token' }, { status: 401 });
}
if (!path) {
return NextResponse.json({ message: 'Path is required' }, { status: 400 });
}
// Normalization
let cleanPath = path === 'homepage' ? '/' : path;
if (!cleanPath.startsWith('/')) {
cleanPath = `/${cleanPath}`;
}
console.log(`[Revalidate] Purging cache for: ${cleanPath}`);
// Do revalidation
revalidatePath(cleanPath, 'page');
return NextResponse.json({
revalidated: true,
path: cleanPath,
now: Date.now()
});
} catch (error) {
console.error('[Revalidate Error]', error);
return NextResponse.json({ message: 'Error revalidating' }, { status: 500 });
}
}