All files / web next.config.js

0% Statements 0/169
0% Branches 0/1
0% Functions 0/1
0% Lines 0/169

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170                                                                                                                                                                                                                                                                                                                                                   
const createNextIntlPlugin = require('next-intl/plugin')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts')

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Enable source maps in production for easier debugging
  productionBrowserSourceMaps: true,

  // Generate unique build ID for cache busting
  generateBuildId: async () => {
    // Use git commit hash if available, otherwise timestamp
    const { execSync } = require('child_process')
    try {
      return execSync('git rev-parse --short HEAD').toString().trim()
    } catch {
      return `build-${Date.now()}`
    }
  },

  // Configure cache headers for static assets
  async headers() {
    return [
      {
        // Static assets with content hash - cache forever (immutable)
        source: '/_next/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        // Build manifest and other _next files without hash
        source: '/_next/:path((?!static).*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=0, must-revalidate',
          },
        ],
      },
      {
        // HTML pages - don't cache (or very short cache)
        source: '/:path((?!_next|api).*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=0, must-revalidate',
          },
        ],
      },
    ]
  },

  eslint: {
    ignoreDuringBuilds: true,
  },
  typescript: {
    ignoreBuildErrors: false,
  },
  experimental: {
    optimizePackageImports: [
      // Internal packages
      '@soroban/core',
      '@soroban/client',
      // Icon libraries (HUGE impact - these bundle everything otherwise)
      'lucide-react',
      // Animation libraries
      'framer-motion',
      // Radix UI components
      '@radix-ui/react-accordion',
      '@radix-ui/react-checkbox',
      '@radix-ui/react-collapsible',
      '@radix-ui/react-dialog',
      '@radix-ui/react-dropdown-menu',
      '@radix-ui/react-hover-card',
      '@radix-ui/react-label',
      '@radix-ui/react-popover',
      '@radix-ui/react-progress',
      '@radix-ui/react-radio-group',
      '@radix-ui/react-select',
      '@radix-ui/react-slider',
      '@radix-ui/react-switch',
      '@radix-ui/react-tabs',
      '@radix-ui/react-toast',
      '@radix-ui/react-tooltip',
      // TanStack
      '@tanstack/react-query',
      '@tanstack/react-form',
      '@tanstack/react-virtual',
    ],
    serverComponentsExternalPackages: [
      '@myriaddreamin/typst.ts',
      'sharp',
      'puppeteer',
      '@napi-rs/canvas',
    ],
  },
  transpilePackages: [
    '@soroban/core',
    '@soroban/client',
    '@svg-maps/world',
    '@svg-maps/usa',
    '@tidepool/debug-panel',
  ],
  webpack: (config, { isServer }) => {
    config.experiments = {
      ...config.experiments,
      asyncWebAssembly: true,
      layers: true,
    }

    // Exclude native Node.js modules from client bundle
    // canvas is a jscanify dependency only needed for Node.js, not browser
    if (!isServer) {
      config.externals = [...(config.externals || []), 'canvas']
    }

    // Optimize WASM loading
    if (!isServer) {
      // Enable dynamic imports for better code splitting
      config.optimization = {
        ...config.optimization,
        splitChunks: {
          ...config.optimization.splitChunks,
          cacheGroups: {
            ...config.optimization.splitChunks?.cacheGroups,
            // Create separate chunk for WASM modules
            wasm: {
              test: /\.wasm$/,
              name: 'wasm',
              chunks: 'async',
              enforce: true,
            },
            // Separate typst.ts into its own chunk
            typst: {
              test: /[\\/]node_modules[\\/]@myriaddreamin[\\/]typst.*[\\/]/,
              name: 'typst',
              chunks: 'async',
              enforce: true,
            },
          },
        },
      }

      // Add preload hints for critical WASM files
      config.resolve.fallback = {
        ...config.resolve.fallback,
        fs: false,
        path: false,
      }
    }

    // Fix for WASM modules
    config.module.rules.push({
      test: /\.wasm$/,
      type: 'asset/resource',
    })

    return config
  },
}

module.exports = withBundleAnalyzer(withNextIntl(nextConfig))