Skip to the content.

CDN Capabilities Guide

This document describes the CDN (Content Delivery Network) capabilities available in the Nuxt application.

Overview

The application supports optional CDN integration for serving static assets, improving performance and reducing server load. When a CDN is configured, all static assets (CSS, JS, images, fonts, etc.) can be served from the CDN instead of the origin server.

Configuration

Setting the CDN URL

Configure the CDN using the NUXT_APP_CDN_URL environment variable:

export NUXT_APP_CDN_URL=https://cdn.example.com
npm run dev

Or in your .env file:

NUXT_APP_CDN_URL=https://cdn.example.com

Local Development (No CDN)

By default, if NUXT_APP_CDN_URL is not set, the application runs without CDN. All assets are served from the origin server.

npm run dev
# Assets served from: http://localhost:4200/...

Production Deployment

For production, set the CDN URL before building:

NUXT_APP_CDN_URL=https://cdn.yourdomain.com npm run build

Alternatively, set it as an environment variable in your deployment platform.

Usage

1. In Components (useCdn Composable)

Use the useCdn() composable to work with CDN URLs in Vue components:

<script setup lang="ts">
import { useCdn } from '#shared/utils/cdn';

const { cdnUrl, resolvePath, isEnabled } = useCdn();

const heroImageUrl = resolvePath('/images/hero.webp');
</script>

<template>
  <div>
    <img v-if="isEnabled" :src="heroImageUrl" alt="Hero" />
    <p>CDN URL: </p>
  </div>
</template>

2. In Utilities and Server Code

Use the CDN utilities for server-side operations:

import {
  resolveCdnPath,
  resolveCdnPaths,
  createCdnHelper,
  buildCdnUrl,
  isCdnUrl,
  stripCdnPrefix,
} from '#shared/utils/cdn';

// Resolve a single path
const imageUrl = resolveCdnPath('/images/logo.png', 'https://cdn.example.com');
// Result: 'https://cdn.example.com/images/logo.png'

// Resolve multiple paths
const urls = resolveCdnPaths(['/css/main.css', '/js/app.js'], 'https://cdn.example.com');

// Create a helper for repeated operations
const cdn = createCdnHelper('https://cdn.example.com');
console.log(cdn.resolve('/images/icon.svg'));
console.log(cdn.isEnabled); // true

// Build URLs with multiple segments
const url = buildCdnUrl('https://cdn.example.com', 'images', 'hero.webp');

// Check if URL is from CDN
const isCdn = isCdnUrl('https://cdn.example.com/images/logo.png', 'https://cdn.example.com');

// Strip CDN prefix from URL
const relativePath = stripCdnPrefix('https://cdn.example.com/images/logo.png', 'https://cdn.example.com');
// Result: '/images/logo.png'

3. In Plugins

Access CDN configuration from plugins using $cdn:

export default defineNuxtPlugin(({ $cdn }) => {
  console.log($cdn.url); // CDN URL or empty string
  console.log($cdn.enabled); // boolean
  console.log($cdn.resolve('/images/logo.png')); // Resolve asset path
});

4. In Runtime Configuration

Access CDN URL from useRuntimeConfig():

const config = useRuntimeConfig();
const cdnUrl = config.public.cdnUrl; // CDN URL or undefined

How It Works

Build Time

When building with a CDN URL set:

  1. Nuxt app.cdnURL: Hashed _nuxt assets resolve to the CDN origin (vite.base stays / so routes like /admin keep working)
  2. Favicon <link>: rel=icon uses ${CDN_URL}/favicon.ico
  3. Public sync: SAM uploads .output/public (including favicon.ico) to the assets bucket / CloudFront

Runtime

At runtime:

  1. Plugins Inject: The $cdn object is injected into the Nuxt app
  2. Composables Available: useCdn() is available in components
  3. Utilities Ready: CDN utilities can be imported and used anywhere
  4. Middleware Active: On SAM, Nitro does not include .output/public. GET/HEAD for static extensions (including /favicon.ico) 302 to runtimeConfig.public.cdnUrl (CloudFront). HTML and /api/** stay on API Gateway.

Lambda vs CloudFront

Artifact Where it runs Contents
Lambda (CodeUribuild-src/server) API Gateway Nitro SSR / API only — no public/
CloudFront + S3 (test) shared-cdn-test (NUXT_APP_CDN_URL) Nitro .output/public at bucket root (shared with HyperActivity; sync without --delete)
CloudFront + S3 (prod) stack AssetsDistribution Nitro .output/public (exclusive bucket; sync with --delete)

sam-build copies server and public into sibling dirs under infra/sam/.aws-sam/build-src/. Nested server/public is rejected. After deploy, CD syncs build-src/public to the CDN bucket root (test → shared; prod → stack bucket).

Website HTML hits *.execute-api.*.amazonaws.com. Browsers may still request /favicon.ico on the API host; CDN middleware 302s to CloudFront when runtimeConfig.public.cdnUrl is set. <link rel="icon"> is rewritten to the CDN at build time when NUXT_APP_CDN_URL is set.

Long-lived cache and CORS for static files belong on the CloudFront distribution, not on Lambda. Do not invalidate /* on the shared test distribution (busts both apps); hashed /_nuxt files do not need invalidation.

CORS Headers

API CORS remains API Gateway’s concern (CORS_ALLOW_ORIGIN). Static files on CloudFront use the distribution’s cache/CORS policy.

Environment Variables

Variable Description Default Example
NUXT_APP_CDN_URL Base URL for CDN empty https://cdn.example.com
NODE_ENV Node environment - production
DEPLOYMENT Deployment target client client, remote

Examples

Example 1: AWS CloudFront

# Build for AWS CloudFront distribution
NUXT_APP_CDN_URL=https://d123456789abcdef.cloudfront.net npm run build

Example 2: Cloudflare CDN

# Build for Cloudflare
NUXT_APP_CDN_URL=https://cdn.yourdomain.com npm run build

Example 3: Local Development (No CDN)

# Assets served directly from dev server
npm run dev

Example 4: Docker Container

FROM node:24-alpine

WORKDIR /app
COPY . .

# Build with CDN configuration
ENV NUXT_APP_CDN_URL=https://cdn.example.com
RUN npm ci && npm run build

EXPOSE 3000
CMD ["npm", "start"]

Performance Benefits

When using a CDN:

  1. Reduced Latency: Assets served from edge locations closer to users
  2. Lower Bandwidth: Origin server bandwidth usage reduced
  3. Better Caching: Static assets cached at CDN edge for longer periods
  4. Global Distribution: Serve assets to users worldwide efficiently
  5. Improved TTFB: Time to First Byte reduced for users

Troubleshooting

Assets Not Loading from CDN

Issue: Assets still load from origin server instead of CDN

Solution:

  1. Verify NUXT_APP_CDN_URL is set before building: echo $NUXT_APP_CDN_URL
  2. Rebuild after setting CDN URL: npm run build
  3. Check browser network tab to see actual asset URLs
  4. Verify CDN is properly configured and accessible

Mixed Content Warning

Issue: HTTPS page with HTTP CDN causes mixed content warning

Solution: Ensure CDN URL uses HTTPS: NUXT_APP_CDN_URL=https://cdn.example.com

CORS Errors

Issue: Cross-origin requests blocked when loading from CDN

Solution: CDN middleware sets CORS headers on static asset paths only (pathname endsWith a known extension; query/hash are ignored so ?x=.js cannot fake an asset). Verify CDN origin is properly configured in your CDN/API Gateway CORS policy for HTML and API traffic.

Further Reading