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:
- Nuxt
app.cdnURL: Hashed_nuxtassets resolve to the CDN origin (vite.basestays/so routes like/adminkeep working) - Favicon
<link>:rel=iconuses${CDN_URL}/favicon.ico - Public sync: SAM uploads
.output/public(includingfavicon.ico) to the assets bucket / CloudFront
Runtime
At runtime:
- Plugins Inject: The
$cdnobject is injected into the Nuxt app - Composables Available:
useCdn()is available in components - Utilities Ready: CDN utilities can be imported and used anywhere
- Middleware Active: On SAM, Nitro does not include
.output/public. GET/HEAD for static extensions (including/favicon.ico) 302 toruntimeConfig.public.cdnUrl(CloudFront). HTML and/api/**stay on API Gateway.
Lambda vs CloudFront
| Artifact | Where it runs | Contents |
|---|---|---|
Lambda (CodeUri → build-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:
- Reduced Latency: Assets served from edge locations closer to users
- Lower Bandwidth: Origin server bandwidth usage reduced
- Better Caching: Static assets cached at CDN edge for longer periods
- Global Distribution: Serve assets to users worldwide efficiently
- 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:
- Verify
NUXT_APP_CDN_URLis set before building:echo $NUXT_APP_CDN_URL - Rebuild after setting CDN URL:
npm run build - Check browser network tab to see actual asset URLs
- 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.
Related Files
app/plugins/cdn.server.ts- Server-side CDN pluginapp/plugins/cdn.client.ts- Client-side CDN pluginapp/composables/useCdn.ts- Vue composable for CDN operations/server/middleware/cdn.ts- Server middleware for CDN headers/shared/utils/cdn.ts- Universal CDN utilities/nuxt.config.ts- Nuxt configuration with CDN settings