From 036fba8b532df9eee859d4dc39ece5e06d57c4f1 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 12 Mar 2026 13:12:13 +0100 Subject: [PATCH] feat(payload): add redirect settings to pages --- app/[locale]/[slug]/page.tsx | 11 ++++++++++- lib/pages.ts | 4 ++++ payload-types.ts | 30 +++++++++++++++++++++++++++--- src/payload/collections/Pages.ts | 27 +++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/app/[locale]/[slug]/page.tsx b/app/[locale]/[slug]/page.tsx index b17c167b..e6b00602 100644 --- a/app/[locale]/[slug]/page.tsx +++ b/app/[locale]/[slug]/page.tsx @@ -1,4 +1,4 @@ -import { notFound, redirect } from 'next/navigation'; +import { notFound, redirect, permanentRedirect } from 'next/navigation'; import { Container, Badge, Heading } from '@/components/ui'; import { getTranslations, setRequestLocale } from 'next-intl/server'; import { Metadata } from 'next'; @@ -62,6 +62,15 @@ export default async function StandardPage({ params }: PageProps) { notFound(); } + // Handle explicit CMS redirects (e.g. /en/terms -> /de/terms) + if (pageData.redirectUrl) { + if (pageData.redirectPermanent) { + permanentRedirect(pageData.redirectUrl); + } else { + redirect(pageData.redirectUrl); + } + } + // Redirect if accessed via a different locale's slug const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale); const correctSlug = await mapFileSlugToTranslated(fileSlug, locale); diff --git a/lib/pages.ts b/lib/pages.ts index f39667ad..cbd09439 100644 --- a/lib/pages.ts +++ b/lib/pages.ts @@ -15,6 +15,8 @@ export interface PageFrontmatter { export interface PageData { slug: string; + redirectUrl?: string; + redirectPermanent?: boolean; frontmatter: PageFrontmatter; content: any; // Lexical AST Document } @@ -96,6 +98,8 @@ export async function getPageBySlug(slug: string, locale: string): Promise | ProductsSelect; pages: PagesSelect | PagesSelect; 'payload-kv': PayloadKvSelect | PayloadKvSelect; - 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; + 'payload-locked-documents': + | PayloadLockedDocumentsSelect + | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; }; @@ -98,6 +100,9 @@ export interface Config { globals: {}; globalsSelect: {}; locale: 'de' | 'en'; + widgets: { + collections: CollectionsWidget; + }; user: User; jobs: { tasks: unknown; @@ -328,6 +333,14 @@ export interface Page { layout?: ('default' | 'fullBleed') | null; excerpt?: string | null; featuredImage?: (number | null) | Media; + /** + * If set, visiting this page will immediately redirect the user to this URL (e.g. /de/terms). + */ + redirectUrl?: string | null; + /** + * Check for a permanent (301) redirect. Uncheck for a temporary (302) redirect. + */ + redirectPermanent?: boolean | null; content: { root: { type: string; @@ -574,6 +587,8 @@ export interface PagesSelect { layout?: T; excerpt?: T; featuredImage?: T; + redirectUrl?: T; + redirectPermanent?: T; content?: T; updatedAt?: T; createdAt?: T; @@ -619,6 +634,16 @@ export interface PayloadMigrationsSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "collections_widget". + */ +export interface CollectionsWidget { + data?: { + [k: string]: unknown; + }; + width: 'full'; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "StatsBlock". @@ -957,7 +982,6 @@ export interface Auth { [k: string]: unknown; } - declare module 'payload' { export interface GeneratedTypes extends Config {} -} \ No newline at end of file +} diff --git a/src/payload/collections/Pages.ts b/src/payload/collections/Pages.ts index 517ce61f..63f82b6a 100644 --- a/src/payload/collections/Pages.ts +++ b/src/payload/collections/Pages.ts @@ -72,6 +72,33 @@ export const Pages: CollectionConfig = { position: 'sidebar', }, }, + { + type: 'collapsible', + label: 'Redirect Settings', + admin: { + position: 'sidebar', + }, + fields: [ + { + name: 'redirectUrl', + type: 'text', + localized: true, + admin: { + description: + 'If set, visiting this page will immediately redirect the user to this URL (e.g. /de/terms).', + }, + }, + { + name: 'redirectPermanent', + type: 'checkbox', + defaultValue: true, + admin: { + description: + 'Check for a permanent (301) redirect. Uncheck for a temporary (302) redirect.', + }, + }, + ], + }, { name: 'content', type: 'richText',