[{"data":1,"prerenderedAt":741},["ShallowReactive",2],{"article-categories":3,"article-tags":12,"latest-articles-5":92,"published-articles":113,"/article/cloudflare-trailing-slash":204},[4,8],{"slug":5,"label":6,"count":7},"blog","Blog",20,{"slug":9,"label":10,"count":11},"book","Books",1,[13,16,18,21,23,27,29,31,33,35,38,40,41,43,45,48,50,53,55,57,59,61,63,65,67,69,72,75,78,80,82,84,86,88,90],{"name":14,"slug":14,"count":15},"beginner",4,{"name":17,"slug":17,"count":15},"linux",{"name":19,"slug":20,"count":15},"nuxt.js","nuxt-js",{"name":22,"slug":22,"count":15},"vim",{"name":24,"slug":25,"count":26},"@nuxt/content","nuxt-content",2,{"name":28,"slug":28,"count":26},"cloudflare",{"name":30,"slug":30,"count":26},"mysql",{"name":32,"slug":32,"count":26},"tools",{"name":34,"slug":34,"count":11},"architecture",{"name":36,"slug":37,"count":11},"AWS","aws",{"name":39,"slug":39,"count":11},"biome",{"name":9,"slug":9,"count":11},{"name":42,"slug":42,"count":11},"css",{"name":44,"slug":44,"count":11},"eslint",{"name":46,"slug":47,"count":11},"ExpoGo","expogo",{"name":49,"slug":49,"count":11},"git",{"name":51,"slug":52,"count":11},"github action","github-action",{"name":54,"slug":54,"count":11},"index",{"name":56,"slug":56,"count":11},"ios",{"name":58,"slug":58,"count":11},"markdown",{"name":60,"slug":60,"count":11},"neovim",{"name":62,"slug":62,"count":11},"obsidian",{"name":64,"slug":64,"count":11},"php",{"name":66,"slug":66,"count":11},"plugins",{"name":68,"slug":68,"count":11},"programing",{"name":70,"slug":71,"count":11},"RDS","rds",{"name":73,"slug":74,"count":11},"ReactNative","reactnative",{"name":76,"slug":77,"count":11},"S3","s3",{"name":79,"slug":79,"count":11},"shell",{"name":81,"slug":81,"count":11},"tailwindcss",{"name":83,"slug":83,"count":11},"terminal",{"name":85,"slug":85,"count":11},"tips",{"name":87,"slug":87,"count":11},"tool",{"name":89,"slug":89,"count":11},"trigger",{"name":91,"slug":91,"count":11},"zsh",[93,97,101,105,109],{"path":94,"title":95,"date":96},"/article/mysql-before-delete-trigger","MySQLのBEFORE DELETEトリガーをちゃんと調べる","2026-09-14",{"path":98,"title":99,"date":100},"/article/eslint-biome-migration","ESLintとPrettierをBiomeやoxlintに乗り換えられるか試す","2026-09-07",{"path":102,"title":103,"date":104},"/article/cloudflare-trailing-slash","Cloudflare Pagesが付ける末尾スラッシュで、記事が「無い」ことになる","2026-09-02",{"path":106,"title":107,"date":108},"/article/tailwind-css-variables","Tailwindのトークンを1箇所にまとめても、ダークモードは切り替わらない","2026-09-01",{"path":110,"title":111,"date":112},"/article/s3-to-rds","AmazonS3のファイルをRDSにロードして使う","2026-02-18",[114,116,118,120,122,124,129,134,139,144,149,154,159,164,169,174,179,184,189,194,199],{"path":94,"title":95,"date":96,"tags":115,"category":5},[30,89],{"path":98,"title":99,"date":100,"tags":117,"category":5},[44,39,19],{"path":102,"title":103,"date":104,"tags":119,"category":5},[28,19,24],{"path":106,"title":107,"date":108,"tags":121,"category":5},[81,19,42],{"path":110,"title":111,"date":112,"tags":123,"category":5},[36,76,70],{"path":125,"title":126,"date":127,"tags":128,"category":5},"/article/tools-lazygit","おすすめツール紹介 - lazygit編","2026-02-10",[87,49,83],{"path":130,"title":131,"date":132,"tags":133,"category":5},"/article/nuxt-content-cloudflare","Nuxt Contentの記事がSSR時に取得できない問題とその解決策","2026-02-07",[19,28,24],{"path":135,"title":136,"date":137,"tags":138,"category":5},"/article/linux-fg-bg","fgとbgでプロセスを操る","2026-02-04",[17],{"path":140,"title":141,"date":142,"tags":143,"category":9},"/article/architecture-textbook","【書評】アーキテクトの教科書","2026-01-30",[9,34],{"path":145,"title":146,"date":147,"tags":148,"category":5},"/article/shell-dev-null","Linuxの標準出力をきちんと理解する","2026-01-28",[79,17],{"path":150,"title":151,"date":152,"tags":153,"category":5},"/article/shell-utilities","シェルの小技・今後使っていきたい機能","2026-01-27",[91,17],{"path":155,"title":156,"date":157,"tags":158,"category":5},"/article/obsidian-action","obsidianのリポジトリをGithubActionsで自動整形する","2026-01-26",[62,51],{"path":160,"title":161,"date":162,"tags":163,"category":5},"/article/react-native-reduce","ReactNativeでのiOS実機確認(Expo Go)で、アプリが動かない事象とその解決法","2026-01-25",[73,46,56],{"path":165,"title":166,"date":167,"tags":168,"category":5},"/article/php-null","phpのnull合体演算子を深掘りする","2026-01-24",[64,68],{"path":170,"title":171,"date":172,"tags":173,"category":5},"/article/mysql-index","6,500万件のテーブルにindexを貼る","2026-01-22",[30,54],{"path":175,"title":176,"date":177,"tags":178,"category":5},"/article/render-markdown","render-markdown.nvimでlinkがうまくレンダリングされなくなった","2026-01-18",[60,66,14],{"path":180,"title":181,"date":182,"tags":183,"category":5},"/article/vim-abbreviation","vimで略語展開設定してみる","2026-01-17",[22,85,14],{"path":185,"title":186,"date":187,"tags":188,"category":5},"/article/vim-folding","neovimのmarkdown折りたたみを改造した","2026-01-16",[22,58],{"path":190,"title":191,"date":192,"tags":193,"category":5},"/article/archlinux-install"," Arch Linux インストールの悪戦苦闘メモ","2026-01-14",[17,14],{"path":195,"title":196,"date":197,"tags":198,"category":5},"/article/vim-standard-memo","Vim標準機能で使いたいものメモ","2026-01-11",[22,32],{"path":200,"title":201,"date":202,"tags":203,"category":5},"/article/vim-command","vim標準で使いたいコマンド","2026-01-10",[22,32,14],{"id":205,"title":103,"body":206,"category":5,"date":104,"description":733,"extension":734,"meta":735,"navigation":736,"path":102,"published":736,"seo":737,"stem":738,"tags":739,"__hash__":740},"article/article/cloudflare-trailing-slash.md",{"type":207,"value":208,"toc":725},"minimark",[209,214,253,256,260,263,270,274,279,320,323,331,334,338,348,354,368,378,384,391,490,504,537,540,544,549,656,666,681,688,695,698,721],[210,211,213],"h2",{"id":212},"前提条件環境","前提条件・環境",[215,216,217,225,236,239],"ul",{},[218,219,220,221,224],"li",{},"Nuxt 4.2 / ",[222,223,24],"code",{}," 3.9",[218,226,227,228,231,232,235],{},"Nitro の preset は ",[222,229,230],{},"cloudflare-pages","。",[222,233,234],{},"crawlLinks"," で全ページをプリレンダ（静的生成）",[218,237,238],{},"デプロイ先は Cloudflare Pages",[218,240,241,242,231,245,248,249,252],{},"記事ページは動的ルート ",[222,243,244],{},"app/pages/article/[_slug].vue",[222,246,247],{},"useAsyncData"," の中で ",[222,250,251],{},"queryCollection('article')"," を叩いて本文を取っている",[210,254,255],{"id":255},"発生した問題",[257,258,259],"p",{},"本番の記事ページをリロードすると、本文が一瞬消えて「Article Not Found」のカードが出ます。記事は存在しているのに、です。",[257,261,262],{},"サイト内のリンクを踏んだ画面遷移では起きません。初回アクセスとリロード、つまりサーバから配信されたHTMLをブラウザが受け取り、Vue がそれを引き継ぐタイミングでだけ起きます。",[257,264,265,266,269],{},"そしてローカルの ",[222,267,268],{},"npm run dev"," では一度も再現しませんでした。",[210,271,273],{"id":272},"wrangler-pages-dev-で再現する","wrangler pages dev で再現する",[257,275,276,278],{},[222,277,268],{}," で出ないのは当然で、この不具合の引き金を引いているのは Nuxt ではなくホスティング側だからです。手元で Cloudflare のランタイムを動かします。",[280,281,286],"pre",{"className":282,"code":283,"language":284,"meta":285,"style":285},"language-sh shiki shiki-themes github-light github-dark","npm run generate\nnpx wrangler pages dev dist\n","sh","",[222,287,288,303],{"__ignoreMap":285},[289,290,292,296,300],"span",{"class":291,"line":11},"line",[289,293,295],{"class":294},"sScJk","npm",[289,297,299],{"class":298},"sZZnC"," run",[289,301,302],{"class":298}," generate\n",[289,304,305,308,311,314,317],{"class":291,"line":26},[289,306,307],{"class":294},"npx",[289,309,310],{"class":298}," wrangler",[289,312,313],{"class":298}," pages",[289,315,316],{"class":298}," dev",[289,318,319],{"class":298}," dist\n",[257,321,322],{},"表示されたURLで記事ページを開くと、アドレスバーがこうなります。",[280,324,329],{"className":325,"code":327,"language":328},[326],"language-text","開いたURL   /article/s3-to-rds\n最終的なURL /article/s3-to-rds/\n","text",[222,330,327],{"__ignoreMap":285},[257,332,333],{},"末尾にスラッシュが増えています。付けたのはアプリではありません。",[210,335,337],{"id":336},"原因-ビルド時のパスとブラウザのurlが違う","原因: ビルド時のパスとブラウザのURLが違う",[257,339,340,343,344,347],{},[222,341,342],{},"nuxt generate"," は記事ページをディレクトリ配下の ",[222,345,346],{},"index.html"," として出力します。",[280,349,352],{"className":350,"code":351,"language":328},[326],"dist/article/s3-to-rds/index.html\n",[222,353,351],{"__ignoreMap":285},[257,355,356,357,359,360,363,364],{},"Cloudflare Pages はこの構成を配信するとき、",[222,358,110],{}," へのリクエストを ",[222,361,362],{},"/article/s3-to-rds/"," にリダイレクトしてから返します。リンクを踏んだだけの画面遷移ではこのリダイレクトが挟まらないので、URLも書き換わりません。",[365,366,367],"strong",{},"リロードして初めて、末尾スラッシュ付きのURLでページが読み込まれます。",[257,369,370,371,374,375,377],{},"一方、ビルド成果物の中身は末尾スラッシュを持ちません。生成された ",[222,372,373],{},"_payload.json"," と ",[222,376,24],{}," のデータを実際に覗くと、どちらもこうなっています。",[280,379,382],{"className":380,"code":381,"language":328},[326],"dist/article/s3-to-rds/_payload.json のキー : \"/article/s3-to-rds\"\n@nuxt/content が持つ記事の path            : \"/article/s3-to-rds\"\n",[222,383,381],{"__ignoreMap":285},[257,385,386,387,390],{},"ここに ",[222,388,389],{},"route.path"," をそのまま渡していました。",[280,392,396],{"className":393,"code":394,"language":395,"meta":285,"style":285},"language-ts shiki shiki-themes github-light github-dark","const { data: page } = await useAsyncData(route.path, () =>\n  queryCollection('article').path(route.path).where('published', '=', true).first(),\n)\n","ts",[222,397,398,437,484],{"__ignoreMap":285},[289,399,400,404,408,412,415,419,422,425,428,431,434],{"class":291,"line":11},[289,401,403],{"class":402},"szBVR","const",[289,405,407],{"class":406},"sVt8B"," { ",[289,409,411],{"class":410},"s4XuR","data",[289,413,414],{"class":406},": ",[289,416,418],{"class":417},"sj4cs","page",[289,420,421],{"class":406}," } ",[289,423,424],{"class":402},"=",[289,426,427],{"class":402}," await",[289,429,430],{"class":294}," useAsyncData",[289,432,433],{"class":406},"(route.path, () ",[289,435,436],{"class":402},"=>\n",[289,438,439,442,445,448,451,454,457,460,462,465,468,471,473,476,478,481],{"class":291,"line":26},[289,440,441],{"class":294},"  queryCollection",[289,443,444],{"class":406},"(",[289,446,447],{"class":298},"'article'",[289,449,450],{"class":406},").",[289,452,453],{"class":294},"path",[289,455,456],{"class":406},"(route.path).",[289,458,459],{"class":294},"where",[289,461,444],{"class":406},[289,463,464],{"class":298},"'published'",[289,466,467],{"class":406},", ",[289,469,470],{"class":298},"'='",[289,472,467],{"class":406},[289,474,475],{"class":417},"true",[289,477,450],{"class":406},[289,479,480],{"class":294},"first",[289,482,483],{"class":406},"(),\n",[289,485,487],{"class":291,"line":486},3,[289,488,489],{"class":406},")\n",[257,491,492,494,495,497,498,500,501,503],{},[222,493,389],{}," は2箇所で使われています。",[222,496,247],{}," の第1引数（＝ペイロードのキー）と、クエリの絞り込み条件です。ビルド時は両方 ",[222,499,110],{}," ですが、ブラウザでは両方 ",[222,502,362],{}," になります。それぞれ別々に壊れます。",[215,505,506,520],{},[218,507,508,511,512,515,516,519],{},[365,509,510],{},"キーのずれ:"," サーバ側が ",[222,513,514],{},"\"/article/s3-to-rds\""," で埋めた結果を、クライアントは ",[222,517,518],{},"\"/article/s3-to-rds/\""," で探しにいきます。見つからないので、取得し直しになります",[218,521,522,525,526,529,530,532,533,536],{},[365,523,524],{},"クエリのずれ:"," 取得し直したクエリは ",[222,527,528],{},".path('/article/s3-to-rds/')"," です。記事が持つ ",[222,531,453],{}," は末尾スラッシュなしなので、これは何にも一致せず ",[222,534,535],{},"null"," を返します",[257,538,539],{},"記事はあるのに「無い」という結論だけが残ります。",[210,541,543],{"id":542},"解決策-パスを正規化してから使う","解決策: パスを正規化してから使う",[257,545,546,548],{},[222,547,389],{}," を直接使うのをやめ、末尾スラッシュを落とした値を1つ作って、キーとクエリの両方に渡します。",[280,550,554],{"className":551,"code":552,"language":553,"meta":285,"style":285},"language-diff shiki shiki-themes github-light github-dark","-const { data: page } = await useAsyncData(route.path, () =>\n-  queryCollection('article').path(route.path).where('published', '=', true).first(),\n+const articlePath = computed(() => route.path.replace(/\\/+$/, '') || '/')\n+\n+const { data: page } = await useAsyncData(articlePath.value, () =>\n+  queryCollection('article').path(articlePath.value).where('published', '=', true).first(),\n )\n","diff",[222,555,556,580,597,609,615,634,650],{"__ignoreMap":285},[289,557,560,564,568,572,575,577],{"class":558,"line":11},[291,559],"diff-remove",[289,561,563],{"class":562},"diff-marker s83E4","-",[289,565,567],{"class":566},"s83E4","const { data: page } = await useAsyncData(",[289,569,571],{"class":570},"diff-word s83E4","route",[289,573,574],{"class":566},".",[289,576,453],{"class":570},[289,578,579],{"class":566},", () =>\n",[289,581,583,585,588,590,592,594],{"class":582,"line":26},[291,559],[289,584,563],{"class":562},[289,586,587],{"class":566},"  queryCollection('article').path(",[289,589,571],{"class":570},[289,591,574],{"class":566},[289,593,453],{"class":570},[289,595,596],{"class":566},").where('published', '=', true).first(),\n",[289,598,601,605],{"class":599,"line":486},[291,600],"diff-add",[289,602,604],{"class":603},"diff-marker s9eBZ","+",[289,606,608],{"class":607},"s9eBZ","const articlePath = computed(() => route.path.replace(/\\/+$/, '') || '/')\n",[289,610,612],{"class":611,"line":15},[291,600],[289,613,614],{"class":603},"+\n",[289,616,619,621,623,627,629,632],{"class":617,"line":618},[291,600],5,[289,620,604],{"class":603},[289,622,567],{"class":607},[289,624,626],{"class":625},"diff-word s9eBZ","articlePath",[289,628,574],{"class":607},[289,630,631],{"class":625},"value",[289,633,579],{"class":607},[289,635,638,640,642,644,646,648],{"class":636,"line":637},[291,600],6,[289,639,604],{"class":603},[289,641,587],{"class":607},[289,643,626],{"class":625},[289,645,574],{"class":607},[289,647,631],{"class":625},[289,649,596],{"class":607},[289,651,653],{"class":291,"line":652},7,[289,654,655],{"class":406}," )\n",[257,657,658,661,662,665],{},[222,659,660],{},"|| '/'"," は、ルート（",[222,663,664],{},"/","）を空文字にしないためのものです。",[667,668,671],"callout",{"title":669,"type":670},"片方だけ直しても足りない","warning",[257,672,673,674,677,678,680],{},"クエリの ",[222,675,676],{},".path()"," だけ正規化すると記事は取れるようになりますが、",[222,679,247],{}," のキーがずれたままなので、サーバが埋めたペイロードは使われず毎回取り直しになります。\nペイロードのキーとクエリの条件は、同じ正規化を通した同じ値にします。",[257,682,683,684,687],{},"修正後、",[222,685,686],{},"wrangler pages dev"," で同じURLを開き直すと、カードは出ずに記事だけが表示されます。",[257,689,690,691,694],{},"静的生成したサイトでは、",[365,692,693],{},"最終的なURLを決めるのはビルドではなくホスティングです。"," ビルド時のパスとブラウザのURLは同じとは限らず、末尾スラッシュはその一番よくある食い違いです。パスを何かのキーに使っているなら、両方の値を一度は突き合わせておく価値があります。",[210,696,697],{"id":697},"参考",[215,699,700,709,716],{},[218,701,702],{},[703,704,708],"a",{"href":705,"rel":706},"https://nuxt.com/docs/api/composables/use-async-data",[707],"nofollow","Nuxt: useAsyncData",[218,710,711],{},[703,712,715],{"href":713,"rel":714},"https://developers.cloudflare.com/pages/configuration/serving-pages/",[707],"Cloudflare Pages: Serving Pages",[218,717,718,719],{},"同じ環境で先に踏んだ問題: ",[703,720,131],{"href":130},[722,723,724],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s83E4, html code.shiki .s83E4{--shiki-default:#B31D28;--shiki-dark:#FDAEB7}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":285,"searchDepth":26,"depth":26,"links":726},[727,728,729,730,731,732],{"id":212,"depth":26,"text":213},{"id":255,"depth":26,"text":255},{"id":272,"depth":26,"text":273},{"id":336,"depth":26,"text":337},{"id":542,"depth":26,"text":543},{"id":697,"depth":26,"text":697},"静的生成したページのキーは末尾スラッシュを持たないのに、Cloudflare Pagesはリロード時にURLへ末尾スラッシュを足す。route.pathをそのままクエリに渡すと、記事があるのに無いと判定される。","md",{},true,{"title":103,"description":733},"article/cloudflare-trailing-slash",[28,19,24],"CWZATCcgi8nYMrgP8Q2lQ1JGCibzm8hjBNBNzxie7_E",1790473631768]