import analyzer from '@next/bundle-analyzer'; import { withSentryConfig } from '@sentry/nextjs'; import withSerwistInit from '@serwist/next'; import type { NextConfig } from 'next'; import ReactComponentName from 'react-scan/react-component-name/webpack'; const isProd = process.env.NODE_ENV === 'production'; const buildWithDocker = process.env.DOCKER === 'true'; const enableReactScan = !!process.env.REACT_SCAN_MONITOR_API_KEY; const isUsePglite = process.env.NEXT_PUBLIC_CLIENT_DB === 'pglite'; // if you need to proxy the api endpoint to remote server const basePath = process.env.NEXT_PUBLIC_BASE_PATH; const nextConfig: NextConfig = { basePath, compress: isProd, experimental: { optimizePackageImports: [ 'emoji-mart', '@emoji-mart/react', '@emoji-mart/data', '@icons-pack/react-simple-icons', '@lobehub/ui', 'gpt-tokenizer', ], webVitalsAttribution: ['CLS', 'LCP'], webpackMemoryOptimizations: true, }, async headers() { return [ { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/icons/(.*).(png|jpe?g|gif|svg|ico|webp)', }, { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/images/(.*).(png|jpe?g|gif|svg|ico|webp)', }, { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/videos/(.*).(mp4|webm|ogg|avi|mov|wmv|flv|mkv)', }, { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/screenshots/(.*).(png|jpe?g|gif|svg|ico|webp)', }, { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/og/(.*).(png|jpe?g|gif|svg|ico|webp)', }, { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/favicon.ico', }, { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/favicon-32x32.ico', }, { headers: [ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable', }, ], source: '/apple-touch-icon.png', }, ]; }, logging: { fetches: { fullUrl: true, hmrRefreshes: true, }, }, output: buildWithDocker ? 'standalone' : undefined, outputFileTracingIncludes: buildWithDocker ? { '*': ['public/**/*', '.next/static/**/*'] } : undefined, reactStrictMode: true, redirects: async () => [ { destination: '/sitemap-index.xml', permanent: true, source: '/sitemap.xml', }, { destination: '/sitemap-index.xml', permanent: true, source: '/sitemap-0.xml', }, { destination: '/manifest.webmanifest', permanent: true, source: '/manifest.json', }, { destination: '/discover/assistant/:slug', has: [ { key: 'agent', type: 'query', value: '(?.*)', }, ], permanent: true, source: '/market', }, { destination: '/discover/assistants', permanent: true, source: '/discover/assistant', }, { destination: '/discover/models', permanent: true, source: '/discover/model', }, { destination: '/discover/plugins', permanent: true, source: '/discover/plugin', }, { destination: '/discover/providers', permanent: true, source: '/discover/provider', }, { destination: '/settings/common', permanent: true, source: '/settings', }, { destination: '/chat', permanent: true, source: '/welcome', }, // TODO: 等 V2 做强制跳转吧 // { // destination: '/settings/provider/volcengine', // permanent: true, // source: '/settings/provider/doubao', // }, // we need back /repos url in the further { destination: '/files', permanent: false, source: '/repos', }, ], // when external packages in dev mode with turbopack, this config will lead to bundle error serverExternalPackages: isProd ? ['@electric-sql/pglite'] : undefined, transpilePackages: ['pdfjs-dist', 'mermaid'], webpack(config) { config.experiments = { asyncWebAssembly: true, layers: true, }; // 开启该插件会导致 pglite 的 fs bundler 被改表 if (enableReactScan && !isUsePglite) { config.plugins.push(ReactComponentName({})); } // to fix shikiji compile error // refs: https://github.com/antfu/shikiji/issues/23 config.module.rules.push({ resolve: { fullySpecified: false, }, test: /\.m?js$/, type: 'javascript/auto', }); // https://github.com/pinojs/pino/issues/688#issuecomment-637763276 config.externals.push('pino-pretty'); config.resolve.alias.canvas = false; // to ignore epub2 compile error // refs: https://github.com/lobehub/lobe-chat/discussions/6769 config.resolve.fallback = { ...config.resolve.fallback, zipfile: false, }; return config; }, }; const noWrapper = (config: NextConfig) => config; const withBundleAnalyzer = process.env.ANALYZE === 'true' ? analyzer() : noWrapper; const withPWA = isProd ? withSerwistInit({ register: false, swDest: 'public/sw.js', swSrc: 'src/app/sw.ts', }) : noWrapper; const hasSentry = !!process.env.NEXT_PUBLIC_SENTRY_DSN; const withSentry = isProd && hasSentry ? (c: NextConfig) => withSentryConfig( c, { org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, // For all available options, see: // https://github.com/getsentry/sentry-webpack-plugin#options // Suppresses source map uploading logs during build silent: true, }, { // Enables automatic instrumentation of Vercel Cron Monitors. // See the following for more information: // https://docs.sentry.io/product/crons/ // https://vercel.com/docs/cron-jobs automaticVercelMonitors: true, // Automatically tree-shake Sentry logger statements to reduce bundle size disableLogger: true, // Hides source maps from generated client bundles hideSourceMaps: true, // Transpiles SDK to be compatible with IE11 (increases bundle size) transpileClientSDK: true, // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. (increases server load) // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- // side errors will fail. tunnelRoute: '/monitoring', // For all available options, see: // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ // Upload a larger set of source maps for prettier stack traces (increases build time) widenClientFileUpload: true, }, ) : noWrapper; export default withBundleAnalyzer(withPWA(withSentry(nextConfig) as NextConfig));