Skip to content

[FE-32] Next.js Performance: Security Headers, Image Optimization & Bundle Analysis #1024

@mftee

Description

@mftee

Overview

frontend/next.config.ts is essentially empty — no security headers, no image domain allowlist, no bundle size analysis, and no Content Security Policy. This issue hardens the Next.js configuration and addresses performance bottlenecks.

Technical Details

1. Security Headers (frontend/next.config.ts)

Add HTTP security headers via Next.js config:

const securityHeaders = [
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self' 'unsafe-eval' 'unsafe-inline'", // Next.js requires unsafe-eval in dev
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https://res.cloudinary.com",
      "connect-src 'self' wss://localhost:* https://horizon-testnet.stellar.org",
    ].join('; ')
  },
];
const nextConfig = {
  async headers() {
    return [{ source: '/(.*)', headers: securityHeaders }];
  }
};

2. Image Domain Configuration

Add all external image domains to next.config.ts:

images: {
  remotePatterns: [
    { protocol: 'https', hostname: 'res.cloudinary.com' },
    { protocol: 'https', hostname: '*.amazonaws.com' }, // if S3
  ],
}

3. Bundle Analysis

Install @next/bundle-analyzer:

npm install -D @next/bundle-analyzer

Configure in next.config.ts:

const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true' });
module.exports = withBundleAnalyzer(nextConfig);

Add to package.json: "analyze": "ANALYZE=true next build"

Run the analyzer (npm run analyze) and identify the top 3 largest bundle chunks. For each:

  • If it is a large library used on only one page, convert to dynamic import: const Heavy = dynamic(() => import('./Heavy'), { ssr: false })
  • Document findings in a comment in next.config.ts

4. Remove the Sandbox Page

frontend/app/sandbox/page.tsx is a developer component showcase accessible in production. Either:

  • Delete the file entirely if it is not needed in production, OR
  • Wrap it with a check: if (process.env.NODE_ENV !== 'development') { notFound(); }

Either approach is acceptable — leave a comment explaining the choice.

5. Environment Variable Type Safety

Create frontend/lib/env.ts that uses a simple validation (manual checks since Zod v4 is available):

const env = {
  NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL!,
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
  NEXT_PUBLIC_STRIPE_VAPID_PUBLIC_KEY: process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY,
};
// Validate in development
if (process.env.NODE_ENV === 'development') {
  Object.entries(env).forEach(([key, value]) => {
    if (!value) console.warn(`Missing env var: ${key}`);
  });
}
export default env;

Replace all direct process.env.NEXT_PUBLIC_* references with env.* imports.

Acceptance Criteria

  • curl -I https://localhost:3000/ includes X-Frame-Options: SAMEORIGIN and X-Content-Type-Options: nosniff headers
  • Cloudinary image URLs render correctly with the remotePatterns config
  • npm run analyze runs without errors and opens the bundle analyzer
  • At least one large library is dynamically imported (identified via bundle analyzer)
  • The sandbox page is either deleted or gated behind a NODE_ENV === 'development' check
  • frontend/lib/env.ts exists and is used throughout the codebase
  • No process.env.NEXT_PUBLIC_* references remain outside of lib/env.ts

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions