import { withPayload } from '@payloadcms/next/withPayload' import { legacyWordPressPostRedirects } from './scripts/lib/legacyWordPressRedirects.mjs' /** @type {import('next').NextConfig} */ const allowedImageHost = process.env.NEXT_PUBLIC_SITE_URL || 'https://jorgesolla.com.br' const nextConfig = { distDir: process.env.NEXT_DIST_DIR || '.next', ...(process.env.NEXT_OUTPUT_STANDALONE === '1' ? { output: 'standalone' } : {}), // S47 — the campaign surfaces left the air after the election. The pages are // gone from the tree; the permanent redirects keep the links that already // circulated (and any index entry) pointing at the mandate instead of a 404. async redirects() { return [ // OPS131 — the old WordPress site (jorgesolla.com.br) becomes this app. // The 43 post permalinks are enumerated (date → canonical Teqo path); // the archive/category/tag/author routes below catch the remaining // indexed URLs from the WordPress sitemap, plus the date archives. ...legacyWordPressPostRedirects.map((entry) => ({ ...entry, permanent: true })), { source: '/category/:path*', destination: '/artigos', permanent: true }, { source: '/tag/:path*', destination: '/artigos', permanent: true }, { source: '/author/:path*', destination: '/artigos', permanent: true }, { source: '/feed', destination: '/artigos', permanent: true }, { source: '/comments/feed', destination: '/artigos', permanent: true }, { source: '/page/:path*', destination: '/artigos', permanent: true }, { source: '/home', destination: '/', permanent: true }, { source: '/:year(\\d{4})/:month(\\d{2})/:day(\\d{2})/:slug', destination: '/artigos', permanent: true, }, { source: '/:year(\\d{4})/:month(\\d{2})/:day(\\d{2})', destination: '/artigos', permanent: true, }, { source: '/:year(\\d{4})/:month(\\d{2})', destination: '/artigos', permanent: true }, { source: '/:year(\\d{4})', destination: '/artigos', permanent: true }, { source: '/wp-admin/:path*', destination: '/', permanent: true }, { source: '/wp-login.php', destination: '/', permanent: true }, // S47 — the campaign surfaces left the air after the election. The pages are // gone from the tree; the permanent redirects keep the links that already // circulated (and any index entry) pointing at the mandate instead of a 404. { source: '/conteudos', destination: '/artigos', permanent: true }, { source: '/conteudos/:path*', destination: '/artigos', permanent: true }, { source: '/jingles', destination: '/', permanent: true }, { source: '/jingles/:path*', destination: '/', permanent: true }, { source: '/cards', destination: '/', permanent: true }, { source: '/cards/:path*', destination: '/', permanent: true }, ] }, images: { minimumCacheTTL: 14400, remotePatterns: [ new URL(`${allowedImageHost}/**`), // YouTube video thumbnails (campaign home content board, S2) come from // the platform's CDN — see `src/utilities/socialFeed/youtubeFeed.ts`. new URL('https://i.ytimg.com/**'), // Instagram media thumbnails (campaign home content board, S3) come from // the Graph API's CDN (hosts vary per region) — // see `src/utilities/socialFeed/instagramFeed.ts`. Plain objects (not // `new URL`) on purpose: the URL form pins `search: ''`, and Graph API // media URLs carry a signed query string — the optimizer would reject // them with 400 even on an allowed host. { protocol: 'https', hostname: '*.cdninstagram.com', pathname: '/**' }, // Meta moved Instagram Graph API media to `*.fbcdn.net` // (`instagram..fna.fbcdn.net`, `scontent..fna.fbcdn.net`) — // without this the optimizer answers 400 and every IG cover breaks. { protocol: 'https', hostname: '**.fbcdn.net', pathname: '/**' }, // e2e-only: the YouTube/Instagram stubs (tests/e2e/*-stub.mjs) serve the // fixture thumbnails locally so specs never touch the real network. { protocol: 'http', hostname: 'localhost', pathname: '/thumbs/**' }, ], }, } export default withPayload(nextConfig, { devBundleServerPackages: false })