Fixing Nuxt Content Static Site Trailing Slash 404 Issue on Refresh

Problem Description
When building a static blog with Nuxt Content, I encountered a strange issue:
- Navigating from the homepage to article links works perfectly
- But when refreshing on an article page, there’s a momentary 404, then the content loads
- The URL automatically changes from
/2025/macaito/2025/macai/(adding a trailing slash)
This issue only occurs in production environments (Netlify, Cloudflare Pages, GitHub Pages), not in local development.
Root Cause
1. Nuxt’s Pre-rendering Mechanism
When Nuxt executes nuxt generate for static site generation (SSG), it defaults to converting routes into subfolder structures:
/2025/macai → /2025/macai/index.html
/about → /about/index.htmlThis structure causes browsers to automatically add / at the end of URLs, because they’re actually accessing index.html inside a directory.
2. Nuxt Content’s Exact Path Matching
In [...slug].vue, we query articles using Nuxt Content:
const { data: post } = await useAsyncData(
route.path,
() => queryCollection('content').path(route.path).first(),
)The problem is:
- The article path in the Content database is
/2025/macai(no trailing slash) - But on refresh,
route.pathis/2025/macai/(with trailing slash) - Exact match fails → returns
null→ shows 404
3. Why Does Homepage Navigation Work?
Navigation from the homepage uses Vue Router client-side routing, which doesn’t trigger real HTTP requests, so there’s no trailing slash issue.
Page refresh uses server routing (even on static hosting), loading the actual HTML file, which triggers the trailing slash addition.
Solution
The core solution is to change Nuxt’s static file generation strategy so it no longer generates subfolder structures.
Configure Nitro’s autoSubfolderIndex option in nuxt.config.ts:
// Import environment variable detection tools, like std-env or use process.env directly
// import { isCI } from 'std-env'
export default defineNuxtConfig({
nitro: {
prerender: {
// Disable subfolder index in CI/CD environment (production build)
autoSubfolderIndex: process.env.CI ? false : undefined,
},
},
})Configuration Effect:
- Generates
/2025/macai.htmlinstead of/2025/macai/index.html - Browser loads the corresponding
.htmlfile directly when accessing/2025/macai - URL won’t automatically add trailing slash
- Root path
/still generatesindex.html(Nuxt handles root directory automatically)
Why Only Enable in CI Environment?
- Local development environment (
npm run dev) uses dev server, doesn’t need this config, keeping default behavior avoids unnecessary interference. - This issue mainly exists on static hosting platforms (Netlify/Cloudflare Pages/GitHub Pages), so it only needs to take effect during build deployment.
Technical Details
How autoSubfolderIndex Works
| Config Value | Generated Structure | Access URL | Description |
|---|---|---|---|
true (default) | /path/index.html | /path/ | Traditional subfolder structure, causes trailing slash |
false | /path.html | /path | Flat structure, perfectly solves the problem |
undefined | Auto-detect based on environment | - | Default behavior in dev environment |
Why Not Use router.options.strict?
Initially tried using:
router: {
options: {
strict: true,
},
},But this makes Vue Router strictly distinguish between /path and /path/, actually worsening the problem, because pre-rendering still generates subfolder structure by default, causing URLs with slashes to fail route matching.
Why Not Use Redirect Rules?
Nitro’s routeRules doesn’t support dynamic function redirects:
// ❌ Not supported
routeRules: {
'/**': {
redirect: {
to: (path) => path.endsWith('/') ? path.slice(0, -1) : path
}
},
}Only static redirect rules can be written, unable to handle all dynamic article paths.
Impact Scope
This configuration affects:
- ✅ All article routes (
/2025/macai) - ✅ All page routes (
/about,/archive) - ✅ Dynamic routes (
[...slug].vue) - ✅ Root path
/still remainsindex.html
Won’t affect:
- ❌ API routes (
/api/*) - ❌ Special files (
/atom.xml,/favicon.ico) - ❌ Existing redirect rules
Verification Method
Local Testing
# Generate static site
pnpm generate
# Preview generated site (note: preview server behavior may differ slightly from real environment)
pnpm preview
# Most accurate method is to check generated file structure
ls -la dist/2025/
# Should see macai.html instead of macai/index.htmlProduction Environment Testing
- Deploy to Netlify/Cloudflare Pages/GitHub Pages
- Visit article page (e.g.,
https://example.com/2025/macai) - Refresh page, check:
- Does URL remain
/2025/macai(no trailing slash) - Is there no 404 flash
- Does content load normally
- Does URL remain
Summary
The essence of this problem is the inconsistency between Nuxt pre-rendering’s file structure and Nuxt Content’s path matching mechanism.
The most elegant solution isn’t patching in frontend code (manually removing slashes), but changing the static file generation structure from the source by configuring autoSubfolderIndex: false. This not only solves the 404 issue but also makes the site’s URL structure more standardized and unified.