shumiland-web/next.config.ts
Vadym Samoilenko 4654bc0e28 feat: Phase 8 — seed, security, loading skeleton, CSP
- src/seed.ts: idempotent seed script (admin, labels, SiteSettings, Navigation, TicketsConfig, homepage, sample blog/event)
- src/app/(frontend)/loading.tsx: page skeleton loader with animate-pulse
- next.config.ts: CSP + security headers (X-Frame-Options, Referrer-Policy, Permissions-Policy), localhost added to image remotePatterns
- src/app/api/tickets/create/route.ts: rate limiting (10 req/min/IP)
- src/app/api/tickets/webhook/route.ts: X-Webhook-Secret or ?secret query param verification
- .env.example: EZY_WEBHOOK_SECRET, SEED_ADMIN_*, portal URLs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 17:40:25 +01:00

55 lines
2 KiB
TypeScript

import { withPayload } from '@payloadcms/next/withPayload'
import type { NextConfig } from 'next'
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=(self)' },
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
// Scripts: self, GTM, GA4, Umami, inline eval for Next.js
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://eu.umami.is https://cdn.binotel.com",
// Styles: self + inline (Tailwind/Next.js inject inline styles)
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
// Fonts
"font-src 'self' https://fonts.gstatic.com",
// Images: self + data URIs (QR codes) + our CDN
"img-src 'self' data: blob: https://shumiland.com.ua https://www.google-analytics.com",
// Connects: self + analytics + ezy + Binotel
"connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://www.ezy.com.ua https://eu.umami.is https://api.binotel.com",
// Media: self
"media-src 'self' https://shumiland.com.ua",
// Frames: ezy payment page
"frame-src 'self' https://www.ezy.com.ua https://www.monobank.ua",
// Workers
"worker-src 'self' blob:",
].join('; '),
},
]
const nextConfig: NextConfig = {
output: 'standalone',
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'shumiland.com.ua' },
{ protocol: 'http', hostname: 'localhost' },
],
},
experimental: {
reactCompiler: false,
},
async headers() {
return [
{
// Apply security headers to all routes except Payload admin (which needs its own)
source: '/((?!admin).*)',
headers: securityHeaders,
},
]
},
}
export default withPayload(nextConfig)