feat(payload): add redirect settings to pages
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🧪 QA (push) Successful in 2m6s
Build & Deploy / 🏗️ Build (push) Failing after 15s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s

This commit is contained in:
2026-03-12 13:12:13 +01:00
parent 3e8d5ad8b6
commit 036fba8b53
4 changed files with 68 additions and 4 deletions

View File

@@ -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 { Container, Badge, Heading } from '@/components/ui';
import { getTranslations, setRequestLocale } from 'next-intl/server'; import { getTranslations, setRequestLocale } from 'next-intl/server';
import { Metadata } from 'next'; import { Metadata } from 'next';
@@ -62,6 +62,15 @@ export default async function StandardPage({ params }: PageProps) {
notFound(); 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 // Redirect if accessed via a different locale's slug
const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale); const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale);
const correctSlug = await mapFileSlugToTranslated(fileSlug, locale); const correctSlug = await mapFileSlugToTranslated(fileSlug, locale);

View File

@@ -15,6 +15,8 @@ export interface PageFrontmatter {
export interface PageData { export interface PageData {
slug: string; slug: string;
redirectUrl?: string;
redirectPermanent?: boolean;
frontmatter: PageFrontmatter; frontmatter: PageFrontmatter;
content: any; // Lexical AST Document content: any; // Lexical AST Document
} }
@@ -96,6 +98,8 @@ export async function getPageBySlug(slug: string, locale: string): Promise<PageD
return { return {
slug: doc.slug, slug: doc.slug,
redirectUrl: doc.redirectUrl,
redirectPermanent: doc.redirectPermanent ?? true,
frontmatter: { frontmatter: {
title: doc.title, title: doc.title,
excerpt: doc.excerpt || '', excerpt: doc.excerpt || '',

View File

@@ -87,7 +87,9 @@ export interface Config {
products: ProductsSelect<false> | ProductsSelect<true>; products: ProductsSelect<false> | ProductsSelect<true>;
pages: PagesSelect<false> | PagesSelect<true>; pages: PagesSelect<false> | PagesSelect<true>;
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>; 'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>; 'payload-locked-documents':
| PayloadLockedDocumentsSelect<false>
| PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>; 'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>; 'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
}; };
@@ -98,6 +100,9 @@ export interface Config {
globals: {}; globals: {};
globalsSelect: {}; globalsSelect: {};
locale: 'de' | 'en'; locale: 'de' | 'en';
widgets: {
collections: CollectionsWidget;
};
user: User; user: User;
jobs: { jobs: {
tasks: unknown; tasks: unknown;
@@ -328,6 +333,14 @@ export interface Page {
layout?: ('default' | 'fullBleed') | null; layout?: ('default' | 'fullBleed') | null;
excerpt?: string | null; excerpt?: string | null;
featuredImage?: (number | null) | Media; 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: { content: {
root: { root: {
type: string; type: string;
@@ -574,6 +587,8 @@ export interface PagesSelect<T extends boolean = true> {
layout?: T; layout?: T;
excerpt?: T; excerpt?: T;
featuredImage?: T; featuredImage?: T;
redirectUrl?: T;
redirectPermanent?: T;
content?: T; content?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
@@ -619,6 +634,16 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
updatedAt?: T; updatedAt?: T;
createdAt?: 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 * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "StatsBlock". * via the `definition` "StatsBlock".
@@ -957,7 +982,6 @@ export interface Auth {
[k: string]: unknown; [k: string]: unknown;
} }
declare module 'payload' { declare module 'payload' {
export interface GeneratedTypes extends Config {} export interface GeneratedTypes extends Config {}
} }

View File

@@ -72,6 +72,33 @@ export const Pages: CollectionConfig = {
position: 'sidebar', 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', name: 'content',
type: 'richText', type: 'richText',