Overview
In a Nuxt 3/4 + Nuxt Content environment with trailingSlash: "append" configured, links to static files such as PDFs and images within content may result in 404 errors.
Conditions for Occurrence
This occurs when all of the following conditions are met:
- Using Nuxt 3/4 + Nuxt Content
trailingSlash: "append"is set innuxt.config.ts- There are links to static files (PDFs, images, etc.) in Markdown or content
Problem Details
Symptoms
When writing a link like the following in content:
<a href="/uploads/document.pdf">Download document</a>
The generated HTML converts the link to:
/uploads/document.pdf/
A / is appended to the end, making the static file inaccessible and resulting in a 404 error.
Cause
In Nuxt Content, <a> tags in Markdown are converted to ProseA components.
The default ProseA component (in the @nuxtjs/mdc package) has the following implementation:
<template>
<NuxtLink :href="props.href" :target="props.target">
<slot />
</NuxtLink>
</template>
Since it uses NuxtLink, it is affected by the trailingSlash setting in nuxt.config.ts.
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: {
trailingSlash: "append", // This setting is the cause
},
},
},
});
Solution
Create a custom ProseA component that uses a regular <a> tag for links to static files.
Implementation
Create components/content/ProseA.vue:
<template>
<a v-if="isStaticFile" :href="resolvedHref" :target="props.target">
<slot />
</a>
<NuxtLink v-else :href="props.href" :target="props.target">
<slot />
</NuxtLink>
</template>
<script setup lang="ts">
const props = defineProps({
href: {
type: String,
default: "",
},
target: {
type: String,
default: undefined,
required: false,
},
});
const config = useRuntimeConfig();
const baseURL = config.app.baseURL || "";
const staticFileExtensions = [
".pdf",
".jpg",
".jpeg",
".png",
".gif",
".webp",
".svg",
".zip",
".doc",
".docx",
".xls",
".xlsx",
".ppt",
".pptx",
".csv",
".txt",
];
const isStaticFile = computed(() => {
const href = props.href.toLowerCase();
return staticFileExtensions.some((ext) => href.endsWith(ext));
});
const resolvedHref = computed(() => {
const href = props.href;
// For external links or those already containing baseURL, use as-is
if (href.startsWith("http") || href.startsWith(baseURL)) {
return href;
}
// Add baseURL to internal relative paths (starting with /)
if (href.startsWith("/")) {
return baseURL + href;
}
return href;
});
</script>
Key Points
- Static file detection: Determines whether a file is static based on the file extension
- Use regular
<a>tag: For static files, use a regular<a>tag instead ofNuxtLinkto avoid thetrailingSlashsetting - Add baseURL: When not using
NuxtLink, baseURL is not automatically applied, so it must be added manually
Verification
After building, check the generated HTML:
npm run generate
grep -o 'href="[^"]*\.pdf[^"]*"' .output/public/path/to/page/index.html
Expected output (no trailing /):
href="/base-url/uploads/document.pdf"
Additional Notes
Why Use trailingSlash: “append”
- From an SEO perspective, it is recommended to unify the presence or absence of trailing
/in URLs - Prevents
/pageand/page/from being treated as different URLs (duplicate content issue) - Many sites adopt
append(adding trailing/)
Other Solutions
Use absolute URLs: Write absolute URLs like
https://example.com/uploads/document.pdfin content (though management becomes cumbersome if URLs differ per environment)Remove trailingSlash setting: Change the site-wide setting (need to consider impact on other pages)
Environment
- Nuxt 4.x (the same issue may occur with Nuxt 3.x)
- Nuxt Content v3
- @nuxtjs/mdc