[{"data":1,"prerenderedAt":501},["ShallowReactive",2],{"\u002Fwritings\u002Fnuxt-content-slash-issue":3},{"id":4,"title":5,"body":6,"date":490,"description":491,"extension":492,"meta":493,"navigation":246,"path":495,"seo":496,"stem":497,"tag":498,"ttr":499,"__hash__":500},"writing\u002Fwritings\u002Fnuxt-content-slash-issue.md","How to Fix the Trailing Slash Issue in Nuxt Content",{"type":7,"value":8,"toc":485},"minimark",[9,14,22,30,33,43,53,61,81,84,87,91,120,135,436,439,468,472,475,478,481],[10,11,13],"h2",{"id":12},"the-problem","The problem",[15,16,17,18,21],"p",{},"I really enjoy using Nuxt Content. It’s simple, intuitive, and easy to manage — everything has a clear place and structure.",[19,20],"br",{},"\nHowever, I ran into a strange issue that I can’t quite tell is a bug or just an unfortunate edge case.",[15,23,24,25,29],{},"Nuxt is responsible for handling routes, and each Nuxt Content file is automatically mapped to a route when everything is set up correctly. The problem appears when a trailing slash (",[26,27,28],"code",{},"\u002F",") is added to the URL.",[15,31,32],{},"When that happens, things start to break:",[34,35,36,40],"ul",{},[37,38,39],"li",{},"Sometimes a 404 page is shown",[37,41,42],{},"Other times the page renders, but data fetching silently fails and nothing shows up",[15,44,45,46,52],{},"This isn’t specific to my setup. I checked ",[47,48,49],"a",{"href":49,"rel":50},"https:\u002F\u002Fwww.hrcd.fr",[51],"nofollow"," (a site I’ve taken a lot of inspiration from), and it exhibits the same behavior. Unfortunately, I couldn’t find many other real-world examples to test against, so this investigation is a bit limited.",[15,54,55,56,60],{},"What ",[57,58,59],"em",{},"is"," clear is that this issue doesn’t seem to be deployment-specific. I was able to reproduce it:",[34,62,63,66,74],{},[37,64,65],{},"When generating the site locally",[37,67,68,69],{},"When deploying to ",[47,70,73],{"href":71,"rel":72},"https:\u002F\u002Fpages.cloudflare.com",[51],"Cloudflare Pages",[37,75,68,76],{},[47,77,80],{"href":78,"rel":79},"https:\u002F\u002Fvercel.com\u002Fhome",[51],"Vercel",[15,82,83],{},"(The hrcd.fr site is also hosted on Vercel.)",[15,85,86],{},"For what it’s worth, the fix described below works reliably on Cloudflare Pages. I didn’t bother re-testing it on Vercel and simply deployed my site on Cloudflare.",[10,88,90],{"id":89},"the-fix","The fix",[15,92,93,94,103,104,106,107,109,110,113,114,119],{},"The simplest and most reliable solution is to add a ",[95,96,97,98],"strong",{},"global ",[47,99,102],{"href":100,"rel":101},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fdirectory-structure\u002Fapp\u002Fmiddleware",[51],"route middleware"," that normalizes URLs by removing trailing slashes.",[19,105],{},"\nIf a route ends with ",[26,108,28],{},", we permanently redirect it to the same path ",[57,111,112],{},"without"," the slash. (",[47,115,118],{"href":116,"rel":117},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMiddleware",[51],"What's a middleware?",")",[15,121,122,123,126,127,130,131,134],{},"In your Nuxt project, create a ",[26,124,125],{},"middleware"," directory inside the ",[26,128,129],{},"app"," directory (if it doesn’t already exist), then add a file called ",[26,132,133],{},"trailingSlash.global.ts",":",[136,137,142],"pre",{"className":138,"code":139,"filename":133,"language":140,"meta":141,"style":141},"language-ts shiki shiki-themes gruvbox-light-hard gruvbox-dark-hard gruvbox-dark-hard","export default defineNuxtRouteMiddleware((to) => {\n    \u002F\u002F ignore root and anything that's just \"\u002F\"\n    if (to.path === \"\u002F\" || to.path === \"\") return;\n\n    \u002F\u002F if it ends with \"\u002F\" and isn't just \"\u002F\"\n    if (to.path.endsWith(\"\u002F\")) {\n        const newPath = to.path.slice(0, -1) || \"\u002F\";\n        return navigateTo(\n            { path: newPath, query: to.query, hash: to.hash },\n            { redirectCode: 301 }, \u002F\u002F permanent redirect\n        );\n    }\n});\n","ts","",[26,143,144,180,187,241,248,254,286,337,349,393,412,420,426],{"__ignoreMap":141},[145,146,149,153,156,160,164,167,171,173,177],"span",{"class":147,"line":148},"line",1,[145,150,152],{"class":151},"sA8KM","export",[145,154,155],{"class":151}," default",[145,157,159],{"class":158},"sXcZX"," defineNuxtRouteMiddleware",[145,161,163],{"class":162},"s2xuh","(",[145,165,163],{"class":166},"swc6o",[145,168,170],{"class":169},"sgL72","to",[145,172,119],{"class":166},[145,174,176],{"class":175},"sTCDi"," =>",[145,178,179],{"class":166}," {\n",[145,181,183],{"class":147,"line":182},2,[145,184,186],{"class":185},"sIIdi","    \u002F\u002F ignore root and anything that's just \"\u002F\"\n",[145,188,190,193,196,198,201,204,208,211,214,217,220,223,225,227,229,232,235,238],{"class":147,"line":189},3,[145,191,192],{"class":151},"    if",[145,194,195],{"class":162}," (",[145,197,170],{"class":169},[145,199,200],{"class":166},".",[145,202,203],{"class":169},"path",[145,205,207],{"class":206},"sHSTz"," ===",[145,209,210],{"class":166}," \"",[145,212,28],{"class":213},"snxCs",[145,215,216],{"class":166},"\"",[145,218,219],{"class":151}," ||",[145,221,222],{"class":169}," to",[145,224,200],{"class":166},[145,226,203],{"class":169},[145,228,207],{"class":206},[145,230,231],{"class":166}," \"\"",[145,233,234],{"class":162},") ",[145,236,237],{"class":151},"return",[145,239,240],{"class":166},";\n",[145,242,244],{"class":147,"line":243},4,[145,245,247],{"emptyLinePlaceholder":246},true,"\n",[145,249,251],{"class":147,"line":250},5,[145,252,253],{"class":185},"    \u002F\u002F if it ends with \"\u002F\" and isn't just \"\u002F\"\n",[145,255,257,259,261,263,265,267,269,272,274,276,278,280,283],{"class":147,"line":256},6,[145,258,192],{"class":151},[145,260,195],{"class":162},[145,262,170],{"class":169},[145,264,200],{"class":166},[145,266,203],{"class":169},[145,268,200],{"class":166},[145,270,271],{"class":158},"endsWith",[145,273,163],{"class":162},[145,275,216],{"class":166},[145,277,28],{"class":213},[145,279,216],{"class":166},[145,281,282],{"class":162},")) ",[145,284,285],{"class":166},"{\n",[145,287,289,292,295,298,300,302,304,306,309,311,315,318,321,324,326,329,331,333,335],{"class":147,"line":288},7,[145,290,291],{"class":175},"        const",[145,293,294],{"class":169}," newPath",[145,296,297],{"class":206}," =",[145,299,222],{"class":169},[145,301,200],{"class":166},[145,303,203],{"class":169},[145,305,200],{"class":166},[145,307,308],{"class":158},"slice",[145,310,163],{"class":162},[145,312,314],{"class":313},"sYNTs","0",[145,316,317],{"class":166},",",[145,319,320],{"class":206}," -",[145,322,323],{"class":313},"1",[145,325,234],{"class":162},[145,327,328],{"class":151},"||",[145,330,210],{"class":166},[145,332,28],{"class":213},[145,334,216],{"class":166},[145,336,240],{"class":166},[145,338,340,343,346],{"class":147,"line":339},8,[145,341,342],{"class":151},"        return",[145,344,345],{"class":158}," navigateTo",[145,347,348],{"class":162},"(\n",[145,350,352,355,358,360,362,364,367,369,371,373,376,378,381,383,385,387,390],{"class":147,"line":351},9,[145,353,354],{"class":166},"            {",[145,356,357],{"class":162}," path",[145,359,134],{"class":166},[145,361,294],{"class":169},[145,363,317],{"class":166},[145,365,366],{"class":162}," query",[145,368,134],{"class":166},[145,370,222],{"class":169},[145,372,200],{"class":166},[145,374,375],{"class":169},"query",[145,377,317],{"class":166},[145,379,380],{"class":162}," hash",[145,382,134],{"class":166},[145,384,222],{"class":169},[145,386,200],{"class":166},[145,388,389],{"class":169},"hash",[145,391,392],{"class":166}," },\n",[145,394,396,398,401,403,406,409],{"class":147,"line":395},10,[145,397,354],{"class":166},[145,399,400],{"class":162}," redirectCode",[145,402,134],{"class":166},[145,404,405],{"class":313}," 301",[145,407,408],{"class":166}," },",[145,410,411],{"class":185}," \u002F\u002F permanent redirect\n",[145,413,415,418],{"class":147,"line":414},11,[145,416,417],{"class":162},"        )",[145,419,240],{"class":166},[145,421,423],{"class":147,"line":422},12,[145,424,425],{"class":166},"    }\n",[145,427,429,432,434],{"class":147,"line":428},13,[145,430,431],{"class":166},"}",[145,433,119],{"class":162},[145,435,240],{"class":166},[15,437,438],{},"This middleware:",[34,440,441,447,454,457],{},[37,442,443,444,446],{},"Leaves the root route (",[26,445,28],{},") untouched",[37,448,449,450,453],{},"Only redirects paths that ",[57,451,452],{},"end"," with a trailing slash",[37,455,456],{},"Preserves query parameters and hashes",[37,458,459,460,467],{},"Uses a ",[95,461,462],{},[47,463,466],{"href":464,"rel":465},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTTP\u002FReference\u002FStatus\u002F301",[51],"301 (Permanent Redirect)",", which tells browsers and search engines that the canonical URL no longer includes the trailing slash",[10,469,471],{"id":470},"final-words","Final words",[15,473,474],{},"This isn’t the most elegant solution, and ideally Nuxt Content (or Nuxt’s routing layer) would handle this more gracefully out of the box. Still, until that happens, a small global middleware like this is an effective and low-maintenance workaround.",[15,476,477],{},"If your Nuxt Content pages randomly break, fail to fetch data, or show 404s depending on whether a trailing slash is present, normalizing routes early is a good defensive move — especially for statically generated sites where consistency matters.",[15,479,480],{},"Hopefully this saves someone else a few hours of confusion.",[482,483,484],"style",{},"html pre.shiki code .sA8KM, html code.shiki .sA8KM{--shiki-light:#9D0006;--shiki-default:#FB4934;--shiki-dark:#FB4934}html pre.shiki code .sXcZX, html code.shiki .sXcZX{--shiki-light:#B57614;--shiki-default:#FABD2F;--shiki-dark:#FABD2F}html pre.shiki code .s2xuh, html code.shiki .s2xuh{--shiki-light:#3C3836;--shiki-default:#EBDBB2;--shiki-dark:#EBDBB2}html pre.shiki code .swc6o, html code.shiki .swc6o{--shiki-light:#7C6F64;--shiki-default:#A89984;--shiki-dark:#A89984}html pre.shiki code .sgL72, html code.shiki .sgL72{--shiki-light:#076678;--shiki-default:#83A598;--shiki-dark:#83A598}html pre.shiki code .sTCDi, html code.shiki .sTCDi{--shiki-light:#AF3A03;--shiki-default:#FE8019;--shiki-dark:#FE8019}html pre.shiki code .sIIdi, html code.shiki .sIIdi{--shiki-light:#928374;--shiki-light-font-style:italic;--shiki-default:#928374;--shiki-default-font-style:italic;--shiki-dark:#928374;--shiki-dark-font-style:italic}html pre.shiki code .sHSTz, html code.shiki .sHSTz{--shiki-light:#427B58;--shiki-default:#8EC07C;--shiki-dark:#8EC07C}html pre.shiki code .snxCs, html code.shiki .snxCs{--shiki-light:#79740E;--shiki-default:#B8BB26;--shiki-dark:#B8BB26}html pre.shiki code .sYNTs, html code.shiki .sYNTs{--shiki-light:#8F3F71;--shiki-default:#D3869B;--shiki-dark:#D3869B}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}",{"title":141,"searchDepth":182,"depth":189,"links":486},[487,488,489],{"id":12,"depth":182,"text":13},{"id":89,"depth":182,"text":90},{"id":470,"depth":182,"text":471},"2025-12-31","A weird routing issue with Nuxt Content URLs — and a practical fix","md",{"author":494},"Hassan Qasemi","\u002Fwritings\u002Fnuxt-content-slash-issue",{"title":5,"description":491},"writings\u002Fnuxt-content-slash-issue","Development","3 min read","raUKr3sH0PndZmgimUPRDBbAOSXjRJ_qEijbbl3WUpk",1789387126897]