feat(cms): add redirects plugin + middleware for 301/302 handling
Install @payloadcms/plugin-redirects, configure for 'pages' collection with 301/302 types. Create src/middleware.ts that reads the redirects collection via REST API (cached 5 min) and applies them before Next.js renders — editors can manage redirects from the CMS admin. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f808ad6b42
commit
bf084e37c9
4 changed files with 69 additions and 0 deletions
|
|
@ -42,6 +42,7 @@
|
|||
"@payloadcms/email-resend": "^3.84.1",
|
||||
"@payloadcms/live-preview-react": "^3.84.1",
|
||||
"@payloadcms/next": "^3.84.0",
|
||||
"@payloadcms/plugin-redirects": "^3.84.1",
|
||||
"@payloadcms/plugin-seo": "^3.84.1",
|
||||
"@payloadcms/richtext-lexical": "^3.84.0",
|
||||
"@react-email/components": "^1.0.12",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { buildConfig } from 'payload'
|
|||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import { seoPlugin } from '@payloadcms/plugin-seo'
|
||||
import { redirectsPlugin } from '@payloadcms/plugin-redirects'
|
||||
import { resendAdapter } from '@payloadcms/email-resend'
|
||||
import sharp from 'sharp'
|
||||
import path from 'path'
|
||||
|
|
@ -89,6 +90,13 @@ export default buildConfig({
|
|||
],
|
||||
|
||||
plugins: [
|
||||
redirectsPlugin({
|
||||
collections: ['pages'],
|
||||
redirectTypes: ['301', '302'],
|
||||
overrides: {
|
||||
admin: { group: 'Контент сайту' },
|
||||
},
|
||||
}),
|
||||
seoPlugin({
|
||||
collections: ['pages', 'blog-posts', 'locations'],
|
||||
globals: [
|
||||
|
|
|
|||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
|
|
@ -20,6 +20,9 @@ importers:
|
|||
'@payloadcms/next':
|
||||
specifier: ^3.84.0
|
||||
version: 3.84.1(@types/react@19.2.14)(graphql@16.14.0)(monaco-editor@0.55.1)(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.84.1(graphql@16.14.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)
|
||||
'@payloadcms/plugin-redirects':
|
||||
specifier: ^3.84.1
|
||||
version: 3.84.1(payload@3.84.1(graphql@16.14.0)(typescript@6.0.3))
|
||||
'@payloadcms/plugin-seo':
|
||||
specifier: ^3.84.1
|
||||
version: 3.84.1(@types/react@19.2.14)(monaco-editor@0.55.1)(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.84.1(graphql@16.14.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)
|
||||
|
|
@ -1548,6 +1551,11 @@ packages:
|
|||
next: '>=15.2.9 <15.3.0 || >=15.3.9 <15.4.0 || >=15.4.11 <15.5.0 || >=16.2.2 <17.0.0'
|
||||
payload: 3.84.1
|
||||
|
||||
'@payloadcms/plugin-redirects@3.84.1':
|
||||
resolution: {integrity: sha512-izKKI5Mm6nosetsAxlqbdOKxraYPlHSGy246D4YbsDemAV4CLOpGcQKTaeTW/JAqYt+RlcvzcWXm0qNMX+2MeQ==}
|
||||
peerDependencies:
|
||||
payload: 3.84.1
|
||||
|
||||
'@payloadcms/plugin-seo@3.84.1':
|
||||
resolution: {integrity: sha512-9FYs5ML/eWR/A/rQfHt2NhPzkJWbUx5SN/+lEQ90r3c3Z8CQUVpt4vETXSI9Gxi764lTutIGemuZAJK9WRy3Lw==}
|
||||
peerDependencies:
|
||||
|
|
@ -6988,6 +6996,11 @@ snapshots:
|
|||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@payloadcms/plugin-redirects@3.84.1(payload@3.84.1(graphql@16.14.0)(typescript@6.0.3))':
|
||||
dependencies:
|
||||
'@payloadcms/translations': 3.84.1
|
||||
payload: 3.84.1(graphql@16.14.0)(typescript@6.0.3)
|
||||
|
||||
'@payloadcms/plugin-seo@3.84.1(@types/react@19.2.14)(monaco-editor@0.55.1)(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(sass@1.77.4))(payload@3.84.1(graphql@16.14.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@payloadcms/translations': 3.84.1
|
||||
|
|
|
|||
47
src/middleware.ts
Normal file
47
src/middleware.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import type { NextRequest } from 'next/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
let redirectsCache: { from: string; to: string; type: string }[] | null = null
|
||||
let cacheExpiry = 0
|
||||
|
||||
async function getRedirects(siteURL: string) {
|
||||
if (redirectsCache && Date.now() < cacheExpiry) return redirectsCache
|
||||
|
||||
try {
|
||||
const res = await fetch(`${siteURL}/api/redirects?limit=100&depth=1`, {
|
||||
next: { revalidate: 300 },
|
||||
})
|
||||
if (!res.ok) return []
|
||||
const data = (await res.json()) as {
|
||||
docs: { from: string; to: { url?: string }; redirectType: string }[]
|
||||
}
|
||||
redirectsCache = data.docs.map((r) => ({
|
||||
from: r.from,
|
||||
to: r.to?.url ?? '/',
|
||||
type: r.redirectType,
|
||||
}))
|
||||
cacheExpiry = Date.now() + 5 * 60 * 1000
|
||||
return redirectsCache
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const siteURL = process.env['NEXT_PUBLIC_SITE_URL'] ?? 'http://localhost:3000'
|
||||
const pathname = request.nextUrl.pathname
|
||||
|
||||
const redirects = await getRedirects(siteURL)
|
||||
const match = redirects.find((r) => r.from === pathname)
|
||||
|
||||
if (match) {
|
||||
const status = match.type === '302' ? 302 : 301
|
||||
return NextResponse.redirect(new URL(match.to, request.url), status)
|
||||
}
|
||||
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!_next/static|_next/image|favicon.ico|api/|admin/).*)'],
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue