🙆
Nuxt Content + trailingSlash設定で静的ファイルへのリンクが404になる問題と解決策
概要
Nuxt 3/4 + Nuxt Content の環境で trailingSlash: "append" を設定している場合、コンテンツ内のPDFや画像などの静的ファイルへのリンクが404エラーになることがあります。
発生条件
以下の条件がすべて揃った場合に発生します:
- Nuxt 3/4 + Nuxt Content を使用
-
nuxt.config.tsでtrailingSlash: "append"を設定 - Markdownやコンテンツ内に静的ファイル(PDF、画像など)へのリンクがある
問題の詳細
症状
コンテンツ内で以下のようなリンクを記述した場合:
<a href="/uploads/document.pdf">資料をダウンロード</a>
生成されるHTMLでは、リンクが以下のように変換されてしまいます:
/uploads/document.pdf/
末尾に / が追加されるため、静的ファイルにアクセスできず404エラーになります。
原因
Nuxt Content では、Markdown内の <a> タグは ProseA コンポーネントに変換されます。
デフォルトの ProseA コンポーネント(@nuxtjs/mdc パッケージ内)は以下のような実装になっています:
<template>
<NuxtLink :href="props.href" :target="props.target">
<slot />
</NuxtLink>
</template>
NuxtLink を使用しているため、nuxt.config.ts の trailingSlash 設定の影響を受けます。
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: {
trailingSlash: "append", // この設定が原因
},
},
},
});
解決策
カスタムの ProseA コンポーネントを作成し、静的ファイルへのリンクの場合は通常の <a> タグを使用するようにします。
実装
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;
// 外部リンクまたは既にbaseURLが含まれている場合はそのまま
if (href.startsWith("http") || href.startsWith(baseURL)) {
return href;
}
// 内部の相対パス(/で始まる)にbaseURLを追加
if (href.startsWith("/")) {
return baseURL + href;
}
return href;
});
</script>
ポイント
- 静的ファイルの判定: ファイル拡張子で静的ファイルかどうかを判定
-
通常の
<a>タグを使用: 静的ファイルの場合はNuxtLinkではなく通常の<a>タグを使用することで、trailingSlash設定の影響を回避 -
baseURLの追加:
NuxtLinkを使わない場合、baseURLが自動適用されないため、手動で追加
動作確認
ビルド後、生成されたHTMLを確認:
npm run generate
grep -o 'href="[^"]*\.pdf[^"]*"' .output/public/path/to/page/index.html
期待する出力(末尾に / がない):
href="/base-url/uploads/document.pdf"
補足
なぜ trailingSlash: "append" を使うのか
- SEOの観点から、URLの末尾の
/の有無を統一することが推奨される -
/pageと/page/が別のURLとして扱われる(重複コンテンツ問題)のを防ぐ - 多くのサイトでは
append(末尾に/を追加)を採用
他の解決策
-
絶対URLを使用: コンテンツ内で
https://example.com/uploads/document.pdfのように絶対URLを記述する(ただし、環境ごとにURLが異なる場合は管理が煩雑) -
trailingSlash設定を削除: サイト全体の設定を変更する(他のページへの影響を考慮する必要あり)
環境
- Nuxt 4.x(Nuxt 3.xでも同様の問題が発生する可能性あり)
- Nuxt Content v3
- @nuxtjs/mdc
Discussion