Skip to main content
Glama
next.config.ts7.99 kB
import type { NextConfig } from 'next'; import { withIntlayer } from 'next-intlayer/server'; import { createSecureHeaders } from 'next-secure-headers'; const isProd = process.env.NODE_ENV === 'production'; const secureHeaders = { contentSecurityPolicy: { directives: { defaultSrc: "'self'", styleSrc: [ "'self'", "'unsafe-inline'", "'report-sample'", `*.${process.env.NEXT_PUBLIC_DOMAIN}`, 'fonts.googleapis.com', "'unsafe-inline'", ], styleSrcElem: [ "'self'", "'report-sample'", `*.${process.env.NEXT_PUBLIC_DOMAIN}`, 'fonts.googleapis.com', 'cdn.jsdelivr.net', "'unsafe-inline'", ], scriptSrc: [ "'self'", "'unsafe-eval'", "'unsafe-inline'", '*.youtube.com', ], scriptSrcElem: [ "'self'", 'data:', "'report-sample'", "'unsafe-inline'", `blob: *.${process.env.NEXT_PUBLIC_DOMAIN}`, '*.facebook.net', '*.google-analytics.com', '*.googletagmanager.com', '*.stripe.com', 'cdn.jsdelivr.net', '*.ahrefs.com', '*.youtube.com', ], connectSrc: [ "'self'", 'data:', `*.${process.env.NEXT_PUBLIC_DOMAIN}`, `${process.env.NEXT_PUBLIC_BACKEND_URL}`, `${process.env.NEXT_PUBLIC_SCANNER_API_URL}`, 'fonts.googleapis.com', '*.facebook.net', '*.facebook.com', '*.google-analytics.com', '*.googletagmanager.com', 'github.com', 'api.github.com', 'raw.githubusercontent.com', '*.openai.com', '*.stripe.com', '*.producthunt.com', 'cdn.jsdelivr.net', '*.ahrefs.com', '*.star-history.com', '*.vercel.app', 'img.shields.io', '*.googleusercontent.com', '*.githubusercontent.com', ], imgSrc: [ "'self'", 'https:', 'data:', 'raw.githubusercontent.com', 'avatars.githubusercontent.com', '*.googleusercontent.com', '*.githubusercontent.com', ], workerSrc: [ `${process.env.NEXT_PUBLIC_URL}`, `blob: *.${process.env.NEXT_PUBLIC_DOMAIN}`, ], mediaSrc: ["'self'"], formAction: ["'self'"], fontSrc: ["'self'", 'cdn.jsdelivr.net'], objectSrc: [ "'self'", 'data:', `blob: *.${process.env.NEXT_PUBLIC_DOMAIN}`, ], frameSrc: [ "'self'", '*.youtube.com', 'github.dev', 'github.com', '*.github.com', '*.stripe.com', 'stackblitz.com', ], frameAncestors: ["'self'", 'intlayer.org', 'localhost:*'], manifestSrc: ["'self'"], childSrc: ["'self'", '*.googletagmanager.com'], }, }, ...(isProd ? { forceHTTPSRedirect: [ true, { maxAge: 60 * 60 * 24 * 4, includeSubDomains: true }, ], } : {}), referrerPolicy: 'same-origin', } satisfies Parameters<typeof createSecureHeaders>[0]; const globalHeaders = [ { key: 'Cache-Control', value: 'public, max-age=31536000, stale-while-revalidate=30', }, { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' }, { key: 'Cross-Origin-Embedder-Policy', value: 'same-origin' }, { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload', }, { key: 'X-Frame-Options', value: 'SAMEORIGIN', }, { key: 'X-XSS-Protection', value: '0', // Disables legacy XSS protection }, { key: 'Permissions-Policy', value: 'fullscreen=(self)', }, ]; const defaultHeaders = [ ...createSecureHeaders(secureHeaders), ...globalHeaders, ]; const dashboardHeaders = [ ...createSecureHeaders({ ...secureHeaders, contentSecurityPolicy: { ...secureHeaders.contentSecurityPolicy, directives: { ...secureHeaders.contentSecurityPolicy.directives, connectSrc: ['*'], frameSrc: ['*'], frameAncestors: ['*'], }, }, }), ...globalHeaders, ]; const scannerHeaders = [ ...createSecureHeaders({ ...secureHeaders, contentSecurityPolicy: { ...secureHeaders.contentSecurityPolicy, directives: { ...secureHeaders.contentSecurityPolicy.directives, imgSrc: ['*'], connectSrc: ['*'], }, }, }), ...globalHeaders, ]; const nextConfig: NextConfig = { // Ensure the full @intlayer/docs package (including markdown assets) is shipped with the server bundle serverExternalPackages: ['@intlayer/backend', '@intlayer/docs'], transpilePackages: ['@intlayer/design-system', 'shiki'], experimental: { optimizePackageImports: ['@intlayer/design-system'], }, reactCompiler: true, productionBrowserSourceMaps: true, // cacheComponents: true, images: { // Ensure long-lived caching for optimized remote images (e.g. YouTube thumbnails) // This is a minimum TTL; upstream headers may be higher. minimumCacheTTL: 60 * 60 * 24 * 7, // 7 days remotePatterns: [ { protocol: 'https', hostname: 'avatars.githubusercontent.com', }, ], }, webpack: (config) => { config.module.rules.push({ test: /\.md$/, // This is the asset module. type: 'asset/source', }); config.externals.push({ '@intlayer/backend': '@intlayer/backend', }); return config; }, turbopack: { rules: { '*.md': { as: '*.ts', loaders: ['raw-loader'], }, }, }, typescript: { // Dangerously allow production builds to successfully complete even if // your project has type errors. ignoreBuildErrors: true, }, compiler: { removeConsole: isProd, }, headers: () => [ // Catch-all FIRST { // All page routes, not the api ones source: '/:path((?!api).*)*', headers: defaultHeaders, }, // Scanner overrides catch-all { source: '/:locale/i18n-seo-scanner', headers: scannerHeaders, }, // Dashboard overrides both { source: '/:locale/dashboard/:path*', headers: dashboardHeaders, }, // Static asset caching (more specific, so placed after general routes) { source: '/assets/:path*', headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], }, { source: '/:path*(.mp4|.webp|.png|.jpg)', headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], }, ], async redirects() { return [ { source: '/doc/environment/vite-and-react/tanstack-start', destination: '/doc/environment/tanstack-start', permanent: true, }, { source: '/:locale/doc/environment/vite-and-react/tanstack-start', destination: '/:locale/doc/environment/tanstack-start', permanent: true, }, ]; }, async rewrites() { return { beforeFiles: [ { source: '/:locale/doc/:path*.md', destination: '/:locale/doc/raw/:path*?format=txt', }, { source: '/doc/:path*.md', destination: '/en/doc/raw/:path*?format=txt', }, { source: '/:locale/blog/:path*.md', destination: '/:locale/blog/raw/:path*?format=txt', }, { source: '/blog/:path*.md', destination: '/en/blog/raw/:path*?format=txt', }, { source: '/:locale/frequent-questions/:path*.md', destination: '/:locale/frequent-questions/raw/:path*?format=txt', }, { source: '/frequent-questions/:path*.md', destination: '/en/frequent-questions/raw/:path*?format=txt', }, ], }; }, }; export default withIntlayer(nextConfig);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server