Live Preview es la pantalla dividida del panel de Payload: a un lado el formulario, al otro tu sitio real mostrando el contenido que estás escribiendo. Para un editor es la diferencia entre publicar a ciegas y ver el resultado antes. Montarlo con Next.js son tres piezas pequeñas.
Cómo funciona
El panel carga tu sitio dentro de un iframe. Para que ese iframe muestre un borrador y no el contenido publicado, hay que pedirle a Next.js que active el Draft Mode. Cada vez que guardas, la página debe refrescarse. Las tres piezas:
- Una URL de vista previa en la configuración de Payload.
- Una ruta de API que comprueba tu sesión y activa Draft Mode.
- Un componente cliente en la página que refresca al guardar.
Pieza 1: la URL en la configuración
En payload.config.ts:
admin: {
livePreview: {
collections: ['posts', 'projects'],
breakpoints: [
{ label: 'Móvil', name: 'mobile', width: 375, height: 667 },
{ label: 'Tablet', name: 'tablet', width: 768, height: 1024 },
{ label: 'Escritorio', name: 'desktop', width: 1440, height: 900 },
],
url: ({ data, collectionConfig, locale }) => {
if (!data?.slug || !collectionConfig) return null;
const params = new URLSearchParams({
collection: collectionConfig.slug,
slug: String(data.slug),
locale: locale.code,
});
return `/api/preview?${params}`;
},
},
},La vista previa no apunta directo a la página del artículo, sino a una ruta intermedia, /api/preview, a la que le pasa la colección, el slug y el idioma. Si el documento aún no tiene slug, devuelve null y no hay vista previa. Los breakpoints añaden los botones de móvil, tablet y escritorio.
Pieza 2: la ruta que activa Draft Mode
En app/api/preview/route.ts:
export async function GET(req: NextRequest) {
const params = req.nextUrl.searchParams;
const draft = await draftMode();
if (params.get('exit')) {
draft.disable();
redirect('/');
}
const payload = await payloadClient();
const { user } = await payload.auth({ headers: req.headers });
const collection = params.get('collection');
const slug = params.get('slug');
const locale = params.get('locale');
if (!user || !slug || !isLocale(locale) ||
(collection !== 'posts' && collection !== 'projects')) {
return new Response('Forbidden', { status: 403 });
}
draft.enable();
redirect(collection === 'posts' ? postPath(locale, slug) : projectPath(locale, slug));
}Esta ruta exige sesión del panel. payload.auth({ headers }) comprueba la cookie, y como el iframe está en el mismo origen, la cookie viaja sola. Sin sesión, 403: nadie puede ver tus borradores adivinando la URL. Con sesión, activa Draft Mode y redirige a la URL real del artículo, construida con la misma función que usa el resto del sitio. ?exit=1 lo desactiva.
Pieza 3: la página lee borradores y se refresca
En la página del artículo, preguntas si Draft Mode está activo y se lo pasas a la consulta:
const { isEnabled: draft } = await draftMode();
const post = await getPost({ slug, locale, draft });
// …
{draft && <LivePreviewListener />}Con draft en true, la consulta pide la última versión guardada como borrador, no la publicada. Y LivePreviewListener es un pequeño componente cliente:
'use client';
import { RefreshRouteOnSave } from '@payloadcms/live-preview-react';
import { useRouter } from 'next/navigation';
export function LivePreviewListener() {
const router = useRouter();
return <RefreshRouteOnSave refresh={() => router.refresh()} serverURL={SITE_URL} />;
}Cada vez que se guarda en el panel, RefreshRouteOnSave llama a router.refresh() y Next.js vuelve a renderizar la página en el servidor, con el borrador nuevo.
Dos cosas que conviene saber
- Refresca al guardar, no al teclear. Esta variante usa el refresco del servidor, que es la más simple y no exige que tu componente sepa leer datos en el cliente.
- El panel y el sitio deben compartir origen. Si el panel está en otro dominio, la cookie de sesión no viaja en el iframe y hay que resolver la autenticación de otra manera.
Para qué sirve en la práctica
Quien escribe ve al instante cómo queda un título largo, una tabla o un bloque de código en móvil, sin publicar. Es una de las razones por las que elegimos Payload; el resto está en Payload vs Strapi vs Directus, y cómo encaja en el sitio, en así está construido codegea.com.
Escrito por
Derian Pinto
Founder & Full Stack Developer
Fundador de Codegea. Desarrolla sitios, tiendas y plataformas a medida, e integra y da soporte a Arc XP para medios.