Documentation Index Fetch the complete documentation index at: https://mintlify.com/cloudflare/vinext/llms.txt
Use this file to discover all available pages before exploring further.
Static Export
vinext supports full static site generation, creating HTML files at build time that can be deployed to any static hosting provider—no server required.
Overview
Static export renders all pages to HTML at build time, producing a directory of static files:
dist/
├── index.html
├── about.html
├── blog/
│ ├── post-1.html
│ └── post-2.html
├── 404.html
└── assets/
├── main.js
└── main.css
Benefits :
Deploy to any static host (CDN, S3, GitHub Pages)
Maximum performance (no server rendering overhead)
Zero infrastructure requirements
Perfect for documentation sites, blogs, marketing pages
Limitations :
No server-side rendering (SSR)
No API routes
No dynamic routes without generateStaticParams() or getStaticPaths()
No Incremental Static Regeneration (ISR)
Enable Static Export
Add output: 'export' to your Next.js config:
// next.config.js
module . exports = {
output: 'export' ,
trailingSlash: true , // Optional: add trailing slashes to URLs
};
Then build:
vinext generates static HTML files in the dist/ directory.
Pages Router
Static Pages
Regular pages are rendered to HTML automatically:
// pages/index.tsx
export default function Home () {
return (
< div >
< h1 > Welcome </ h1 >
< p > This will be static HTML </ p >
</ div >
);
}
Generates dist/index.html.
Pages with Data (getStaticProps)
Fetch data at build time:
// pages/about.tsx
interface Props {
data : { title : string ; content : string };
}
export default function About ({ data } : Props ) {
return (
< div >
< h1 >{data. title } </ h1 >
< p >{data. content } </ p >
</ div >
);
}
export async function getStaticProps () {
const data = await fetch ( 'https://api.example.com/about' ). then ( r => r . json ());
return {
props: { data },
};
}
getStaticProps runs at build time—the API is called once, and the result is baked into the HTML.
Dynamic Routes
Dynamic routes require getStaticPaths to specify which paths to generate:
// pages/blog/[slug].tsx
interface Props {
post : { slug : string ; title : string ; content : string };
}
export default function BlogPost ({ post } : Props ) {
return (
< article >
< h1 >{post. title } </ h1 >
< div dangerouslySetInnerHTML = {{ __html : post . content }} />
</ article >
);
}
export async function getStaticPaths () {
const posts = await fetch ( 'https://api.example.com/posts' ). then ( r => r . json ());
return {
paths: posts . map (( post : any ) => ({
params: { slug: post . slug },
})),
fallback: false , // Must be false for static export
};
}
export async function getStaticProps ({ params }) {
const post = await fetch ( `https://api.example.com/posts/ ${ params . slug } ` ). then ( r => r . json ());
return {
props: { post },
};
}
Important : fallback must be false for static export. No dynamic fallback pages are generated.
Catch-All Routes
Catch-all routes work the same way:
// pages/docs/[...slug].tsx
export async function getStaticPaths () {
return {
paths: [
{ params: { slug: [ 'getting-started' ] } },
{ params: { slug: [ 'api' , 'reference' ] } },
{ params: { slug: [ 'guides' , 'deployment' , 'cloudflare' ] } },
],
fallback: false ,
};
}
export async function getStaticProps ({ params }) {
const slug = params . slug . join ( '/' );
const doc = await fetchDoc ( slug );
return {
props: { doc },
};
}
Generates:
dist/docs/getting-started.html
dist/docs/api/reference.html
dist/docs/guides/deployment/cloudflare.html
Not Supported with Static Export
getServerSideProps
Error : getServerSideProps requires a server.
// ❌ This will cause a build error
export async function getServerSideProps () {
return { props: {} };
}
Solution : Use getStaticProps instead.
API Routes
API routes are skipped with a warning:
// pages/api/hello.ts
// ⚠️ Skipped during static export
export default function handler ( req , res ) {
res . json ({ message: 'Hello' });
}
Alternative : Use external APIs or serverless functions.
App Router
Static Pages
Server Components are rendered at build time:
// app/page.tsx
export default function Home () {
return (
< div >
< h1 > Welcome </ h1 >
< p > This is static HTML </ p >
</ div >
);
}
Generates dist/index.html.
Pages with Data
Server Components can fetch data directly:
// app/about/page.tsx
async function getData () {
const res = await fetch ( 'https://api.example.com/about' );
return res . json ();
}
export default async function About () {
const data = await getData ();
return (
< div >
< h1 >{data. title } </ h1 >
< p >{data. content } </ p >
</ div >
);
}
The fetch runs at build time, and the result is baked into the HTML.
Dynamic Routes
Dynamic routes require generateStaticParams:
// app/blog/[slug]/page.tsx
interface Props {
params : { slug : string };
}
export async function generateStaticParams () {
const posts = await fetch ( 'https://api.example.com/posts' ). then ( r => r . json ());
return posts . map (( post : any ) => ({
slug: post . slug ,
}));
}
export default async function BlogPost ({ params } : Props ) {
const post = await fetch ( `https://api.example.com/posts/ ${ params . slug } ` ). then ( r => r . json ());
return (
< article >
< h1 >{post. title } </ h1 >
< div dangerouslySetInnerHTML = {{ __html : post . content }} />
</ article >
);
}
Catch-All Routes
// app/docs/[...slug]/page.tsx
export async function generateStaticParams () {
return [
{ slug: [ 'getting-started' ] },
{ slug: [ 'api' , 'reference' ] },
{ slug: [ 'guides' , 'deployment' ] },
];
}
export default async function DocPage ({ params } : { params : { slug : string [] } }) {
const path = params . slug . join ( '/' );
const doc = await fetchDoc ( path );
return (
< article >
< h1 >{doc. title } </ h1 >
< div dangerouslySetInnerHTML = {{ __html : doc . content }} />
</ article >
);
}
Nested Dynamic Routes
For nested dynamic segments, use top-down params passing:
// app/[category]/[product]/page.tsx
// Parent generates categories
export async function generateStaticParams () {
return [
{ category: 'electronics' },
{ category: 'clothing' },
];
}
// Child receives parent params and generates products
export async function generateStaticParams ({ params } : { params : { category : string } }) {
const products = await fetchProducts ( params . category );
return products . map ( p => ({ product: p . slug }));
}
export default async function ProductPage ({ params } : { params : { category : string ; product : string } }) {
const product = await fetchProduct ( params . category , params . product );
return < div >{product. name } </ div > ;
}
Route Handlers (API Routes)
Route handlers are skipped with a warning:
// app/api/hello/route.ts
// ⚠️ Skipped during static export
export async function GET () {
return Response . json ({ message: 'Hello' });
}
Metadata is included in the generated HTML:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next' ;
export async function generateMetadata ({ params } : { params : { slug : string } }) : Promise < Metadata > {
const post = await fetchPost ( params . slug );
return {
title: post . title ,
description: post . excerpt ,
openGraph: {
title: post . title ,
description: post . excerpt ,
images: [ post . coverImage ],
},
};
}
Build Process
vinext performs the following steps during static export:
Route Discovery
Scans your pages/ or app/ directory to find all routes.
Path Expansion
For dynamic routes, calls getStaticPaths() (Pages Router) or generateStaticParams() (App Router) to expand all possible paths.
Rendering
Renders each route to HTML:
Pages Router: Calls getStaticProps, renders with React SSR
App Router: Starts dev server, fetches each URL, saves HTML
Asset Copying
Copies static assets (JS, CSS, images) from public/ and Vite build output to dist/.
404 Page
Renders custom 404 page if present, otherwise uses default.
Deployment
Cloudflare Pages
Deploy the dist/ directory:
npx wrangler pages deploy dist
Or configure automatic deployments via GitHub integration in the Cloudflare dashboard.
Vercel
npm install -g vercel
vercel --prod
Vercel auto-detects the static output and deploys it.
Netlify
npm install -g netlify-cli
netlify deploy --prod --dir=dist
Or connect your GitHub repo for automatic deployments.
GitHub Pages
Add a workflow:
name : Deploy to GitHub Pages
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : actions/setup-node@v4
with :
node-version : '20'
- name : Install dependencies
run : npm ci
- name : Build
run : npm run build
- name : Deploy
uses : peaceiris/actions-gh-pages@v3
with :
github_token : ${{ secrets.GITHUB_TOKEN }}
publish_dir : ./dist
Configure GitHub Pages to serve from the gh-pages branch.
AWS S3 + CloudFront
# Upload to S3
aws s3 sync dist/ s3://my-bucket --delete
# Invalidate CloudFront cache
aws cloudfront create-invalidation --distribution-id EDFDVBD6EXAMPLE --paths "/*"
Nginx
Copy dist/ to your web server:
scp -r dist/ * user@server:/var/www/html/
Configure Nginx:
server {
listen 80 ;
server_name example.com;
root /var/www/html;
index index.html;
location / {
try_files $ uri $ uri .html $ uri / =404 ;
}
error_page 404 /404.html;
}
Advanced Configuration
Trailing Slash
Control URL format:
module . exports = {
output: 'export' ,
trailingSlash: true ,
};
With trailingSlash: true :
/about → dist/about/index.html
Served as /about/
With trailingSlash: false (default):
/about → dist/about.html
Served as /about
Base Path
Deploy to a subdirectory:
module . exports = {
output: 'export' ,
basePath: '/docs' ,
};
All routes are prefixed with /docs:
/ → /docs/
/about → /docs/about
Links and asset paths are automatically adjusted.
Image Optimization
Images are not optimized in static export. Options:
Disable optimization :
module . exports = {
output: 'export' ,
images: {
unoptimized: true ,
},
};
Use a CDN : Upload images to a CDN that handles optimization (Cloudflare Images, Imgix, Cloudinary)
Optimize at build time : Use an image optimization tool before deploying
Common Patterns
Documentation Site
// app/docs/[...slug]/page.tsx
import fs from 'fs' ;
import path from 'path' ;
import { marked } from 'marked' ;
export async function generateStaticParams () {
const docsDir = path . join ( process . cwd (), 'content/docs' );
const files = getAllMarkdownFiles ( docsDir );
return files . map ( file => ({
slug: file . replace ( '.md' , '' ). split ( '/' ),
}));
}
export default async function DocPage ({ params } : { params : { slug : string [] } }) {
const filePath = path . join ( process . cwd (), 'content/docs' , ... params . slug ) + '.md' ;
const content = fs . readFileSync ( filePath , 'utf-8' );
const html = marked ( content );
return < div dangerouslySetInnerHTML ={{ __html : html }} />;
}
// app/blog/page/[page]/page.tsx
const POSTS_PER_PAGE = 10 ;
export async function generateStaticParams () {
const posts = await fetchAllPosts ();
const pageCount = Math . ceil ( posts . length / POSTS_PER_PAGE );
return Array . from ({ length: pageCount }, ( _ , i ) => ({
page: String ( i + 1 ),
}));
}
export default async function BlogPage ({ params } : { params : { page : string } }) {
const page = parseInt ( params . page );
const posts = await fetchPosts ( page , POSTS_PER_PAGE );
return (
< div >
{ posts . map ( post => (
< article key = {post. id } >
< h2 >{post. title } </ h2 >
< p >{post. excerpt } </ p >
</ article >
))}
</ div >
);
}
Multi-Language Site
// app/[lang]/page.tsx
export async function generateStaticParams () {
return [
{ lang: 'en' },
{ lang: 'fr' },
{ lang: 'de' },
];
}
export default async function HomePage ({ params } : { params : { lang : string } }) {
const content = await loadContent ( params . lang );
return < div >{ content } </ div > ;
}
Troubleshooting
Build Error: getServerSideProps Not Supported
Error : Page uses getServerSideProps which is not supported with output: 'export'
Solution : Replace getServerSideProps with getStaticProps. Server-side rendering requires a server.
Build Error: Dynamic Route Missing Paths
Error : Dynamic route requires getStaticPaths with output: 'export'
Solution : Add getStaticPaths (Pages Router) or generateStaticParams (App Router):
export async function generateStaticParams () {
return [{ slug: 'example' }];
}
404 on Dynamic Routes
Problem : Dynamic routes return 404 after deployment
Solution : Most static hosts expect .html files. Configure your host to:
Append .html to paths without extensions
Use a rewrite rule: /blog/post-1 → /blog/post-1.html
Or enable trailing slashes:
module . exports = {
trailingSlash: true ,
};
This generates /blog/post-1/index.html which is served as /blog/post-1/ by default.
Large Build Time
Problem : Static export takes a long time
Solution : You’re generating many pages. Options:
Reduce the number of pages generated
Use ISR instead (requires a server)
Deploy to Cloudflare Workers for on-demand rendering
Data Not Updating
Problem : Content changes don’t appear after deployment
Solution : Static export bakes data at build time. To update:
Rebuild: vinext build
Redeploy the new dist/ directory
For frequently-changing data, use client-side fetching instead:
'use client' ;
import { useEffect , useState } from 'react' ;
export default function Page () {
const [ data , setData ] = useState ( null );
useEffect (() => {
fetch ( 'https://api.example.com/data' )
. then ( r => r . json ())
. then ( setData );
}, []);
return < div >{data?. message } </ div > ;
}
Next Steps
Deployment Guide Deploy your static site to production
Configuration Customize build and export settings
Cloudflare Pages Deploy to Cloudflare Pages
Examples Explore static export examples