[{"data":1,"prerenderedAt":1659},["ShallowReactive",2],{"blog-deploying-nuxt4-nuxtcontentv3-vercel-with-api-routes":3},{"id":4,"title":5,"body":6,"date":1646,"description":1647,"extension":1648,"meta":1649,"navigation":1453,"path":1650,"seo":1651,"stem":1652,"tags":1653,"__hash__":1658},"blog/blog/deploying-nuxt4-nuxtcontentv3-vercel-with-api-routes.md","Deploying Nuxt 4 + Nuxt Content v3 on Vercel with API Routes",{"type":7,"value":8,"toc":1607},"minimark",[9,12,17,21,55,58,60,64,69,88,98,102,113,217,221,229,247,252,271,280,284,293,354,359,373,378,395,401,403,407,410,421,424,429,454,459,465,469,476,484,486,490,493,499,618,622,634,641,647,650,653,658,693,698,716,720,736,739,755,760,841,846,900,905,907,911,916,1108,1114,1278,1282,1305,1307,1311,1315,1321,1325,1328,1350,1354,1357,1368,1372,1375,1392,1396,1399,1433,1435,1439,1442,1520,1522,1526,1530,1533,1537,1540,1544,1547,1551,1554,1556,1560,1563,1577,1586,1600,1603],[10,11],"hr",{},[13,14,16],"h2",{"id":15},"the-project","The Project",[18,19,20],"p",{},"A portfolio website built with:",[22,23,24,32,38,49],"ul",{},[25,26,27,31],"li",{},[28,29,30],"strong",{},"Nuxt 4"," (latest stable)",[25,33,34,37],{},[28,35,36],{},"Nuxt Content v3"," for blog posts and project pages",[25,39,40,43,44,48],{},[28,41,42],{},"Server API routes"," for a contact form (",[45,46,47],"code",{},"/server/api/contact.post.ts",")",[25,50,51,54],{},[28,52,53],{},"Vercel"," as the deployment target",[18,56,57],{},"The goal was straightforward: deploy a portfolio site with dynamic content management and a working contact form. What followed was a three-stage debugging journey that exposed the nuances of serverless deployments, native Node.js modules, and hybrid rendering.",[10,59],{},[13,61,63],{"id":62},"issue-1-the-500-error-native-binary-mismatch","Issue #1: The 500 Error - Native Binary Mismatch",[65,66,68],"h3",{"id":67},"what-happened","What Happened",[18,70,71,72,75,76,79,80,83,84,87],{},"After deploying to Vercel using the standard ",[45,73,74],{},"nuxt build"," command, all content pages (",[45,77,78],{},"/blog/test",", ",[45,81,82],{},"/work/project-name",", etc.) returned ",[28,85,86],{},"500 Internal Server Error",". The Vercel logs showed:",[89,90,95],"pre",{"className":91,"code":93,"language":94},[92],"language-text","[error] Failed to execute SQL CREATE TABLE IF NOT EXISTS _content_info...\nModule did not self-register: '/var/task/node_modules/better-sqlite3/build/Release/better_sqlite3.node'.\n\nH3Error: Module did not self-register: '/var/task/node_modules/better-sqlite3/build/Release/better_sqlite3.node'.\n  cause: Error: Module did not self-register...\n    code: 'ERR_DLOPEN_FAILED'\n","text",[45,96,93],{"__ignoreMap":97},"",[65,99,101],{"id":100},"initial-setup","Initial Setup",[18,103,104,105,108,109,112],{},"The ",[45,106,107],{},"package.json"," had ",[45,110,111],{},"better-sqlite3"," as a direct dependency:",[89,114,118],{"className":115,"code":116,"language":117,"meta":97,"style":97},"language-json shiki shiki-themes github-light github-dark","{\n  \"dependencies\": {\n    \"@nuxt/content\": \"^3.11.2\",\n    \"better-sqlite3\": \"^12.6.2\",\n    \"nuxt\": \"^4.3.1\",\n    \"vue\": \"^3.5.28\",\n    \"vue-router\": \"^4.6.4\"\n  }\n}\n","json",[45,119,120,129,139,155,168,181,194,205,211],{"__ignoreMap":97},[121,122,125],"span",{"class":123,"line":124},"line",1,[121,126,128],{"class":127},"sVt8B","{\n",[121,130,132,136],{"class":123,"line":131},2,[121,133,135],{"class":134},"sj4cs","  \"dependencies\"",[121,137,138],{"class":127},": {\n",[121,140,142,145,148,152],{"class":123,"line":141},3,[121,143,144],{"class":134},"    \"@nuxt/content\"",[121,146,147],{"class":127},": ",[121,149,151],{"class":150},"sZZnC","\"^3.11.2\"",[121,153,154],{"class":127},",\n",[121,156,158,161,163,166],{"class":123,"line":157},4,[121,159,160],{"class":134},"    \"better-sqlite3\"",[121,162,147],{"class":127},[121,164,165],{"class":150},"\"^12.6.2\"",[121,167,154],{"class":127},[121,169,171,174,176,179],{"class":123,"line":170},5,[121,172,173],{"class":134},"    \"nuxt\"",[121,175,147],{"class":127},[121,177,178],{"class":150},"\"^4.3.1\"",[121,180,154],{"class":127},[121,182,184,187,189,192],{"class":123,"line":183},6,[121,185,186],{"class":134},"    \"vue\"",[121,188,147],{"class":127},[121,190,191],{"class":150},"\"^3.5.28\"",[121,193,154],{"class":127},[121,195,197,200,202],{"class":123,"line":196},7,[121,198,199],{"class":134},"    \"vue-router\"",[121,201,147],{"class":127},[121,203,204],{"class":150},"\"^4.6.4\"\n",[121,206,208],{"class":123,"line":207},8,[121,209,210],{"class":127},"  }\n",[121,212,214],{"class":123,"line":213},9,[121,215,216],{"class":127},"}\n",[65,218,220],{"id":219},"root-cause-analysis","Root Cause Analysis",[18,222,223],{},[28,224,225,226,228],{},"What is ",[45,227,111],{},"?",[22,230,231,234,244],{},[25,232,233],{},"A native Node.js addon (compiled C++ code)",[25,235,236,237,240,241],{},"Produces a ",[45,238,239],{},".node"," binary file during ",[45,242,243],{},"npm install",[25,245,246],{},"The binary is platform-specific (OS + architecture + Node.js version)",[18,248,249],{},[28,250,251],{},"Why did it fail?",[253,254,255,258,261,264],"ol",{},[25,256,257],{},"Vercel's build environment compiled the binary for one environment",[25,259,260],{},"Vercel's Lambda runtime (where the code actually runs) is a different environment",[25,262,263],{},"The pre-compiled binary was incompatible with the Lambda runtime",[25,265,266,267,270],{},"Node.js threw ",[45,268,269],{},"ERR_DLOPEN_FAILED"," when trying to load it",[18,272,273,276,277,279],{},[28,274,275],{},"The mistake:"," Manually adding ",[45,278,111],{}," as a dependency overrode Nuxt Content's internal database adapter selection logic, which is designed to choose the right adapter based on the deployment target.",[65,281,283],{"id":282},"first-solution-attempt","First Solution Attempt",[18,285,286,289,290,292],{},[28,287,288],{},"Action taken:"," Remove ",[45,291,111],{}," from dependencies and switch to static generation.",[89,294,296],{"className":115,"code":295,"language":117,"meta":97,"style":97},"{\n  \"dependencies\": {\n    \"@nuxt/content\": \"^3.11.2\",\n    \"nuxt\": \"^4.3.1\",\n    \"vue\": \"^3.5.28\",\n    \"vue-router\": \"^4.6.4\"\n  }\n}\n",[45,297,298,302,308,318,328,338,346,350],{"__ignoreMap":97},[121,299,300],{"class":123,"line":124},[121,301,128],{"class":127},[121,303,304,306],{"class":123,"line":131},[121,305,135],{"class":134},[121,307,138],{"class":127},[121,309,310,312,314,316],{"class":123,"line":141},[121,311,144],{"class":134},[121,313,147],{"class":127},[121,315,151],{"class":150},[121,317,154],{"class":127},[121,319,320,322,324,326],{"class":123,"line":157},[121,321,173],{"class":134},[121,323,147],{"class":127},[121,325,178],{"class":150},[121,327,154],{"class":127},[121,329,330,332,334,336],{"class":123,"line":170},[121,331,186],{"class":134},[121,333,147],{"class":127},[121,335,191],{"class":150},[121,337,154],{"class":127},[121,339,340,342,344],{"class":123,"line":183},[121,341,199],{"class":134},[121,343,147],{"class":127},[121,345,204],{"class":150},[121,347,348],{"class":123,"line":196},[121,349,210],{"class":127},[121,351,352],{"class":123,"line":207},[121,353,216],{"class":127},[18,355,356],{},[28,357,358],{},"Vercel settings:",[22,360,361,367],{},[25,362,363,364],{},"Build command: ",[45,365,366],{},"npm run generate",[25,368,369,370],{},"Output directory: ",[45,371,372],{},".output/public",[18,374,375],{},[28,376,377],{},"Why this worked (initially):",[22,379,380,386,389,392],{},[25,381,382,385],{},[45,383,384],{},"nuxt generate"," pre-renders all pages to static HTML at build time",[25,387,388],{},"Content is processed during the build (where SQLite works fine)",[25,390,391],{},"Runtime deployment is just static files on Vercel's CDN",[25,393,394],{},"No server, no SQLite, no native binary issues",[18,396,397,400],{},[28,398,399],{},"Result:"," ✅ Content pages worked perfectly. Site deployed successfully.",[10,402],{},[13,404,406],{"id":405},"issue-2-the-missing-api-static-vs-dynamic","Issue #2: The Missing API - Static vs Dynamic",[65,408,68],{"id":409},"what-happened-1",[18,411,412,413,416,417,420],{},"After switching to static generation, the contact form started returning ",[28,414,415],{},"404 Not Found"," errors when submitting. The API endpoint ",[45,418,419],{},"/api/contact"," was completely unavailable.",[65,422,220],{"id":423},"root-cause-analysis-1",[18,425,426],{},[28,427,428],{},"The fundamental trade-off:",[22,430,431,440,451],{},[25,432,433,435,436,439],{},[45,434,384],{}," creates a ",[28,437,438],{},"purely static site"," — just HTML, CSS, and JavaScript files",[25,441,442,443,446,447,450],{},"Server routes (",[45,444,445],{},"/server/api/*",") are ",[28,448,449],{},"not included"," in static builds",[25,452,453],{},"They require a Node.js runtime to execute, which static hosting doesn't provide",[18,455,456],{},[28,457,458],{},"The architecture mismatch:",[89,460,463],{"className":461,"code":462,"language":94},[92],"Static Generation:\n├── Pre-render: Everything → HTML files\n├── Deploy: Only static files\n└── Runtime: No server, no API routes ❌\n\nServer-Side Rendering:\n├── Build: Server bundle + client bundle\n├── Deploy: Server runs on every request\n└── Runtime: SQLite needed ❌ (back to Issue #1)\n",[45,464,462],{"__ignoreMap":97},[65,466,468],{"id":467},"the-real-requirement","The Real Requirement",[18,470,471,472,475],{},"The project needed ",[28,473,474],{},"hybrid rendering",":",[22,477,478,481],{},[25,479,480],{},"Content pages: Static (fast, no SQLite)",[25,482,483],{},"API routes: Dynamic (serverless functions)",[10,485],{},[13,487,489],{"id":488},"issue-3-the-stuck-build-timing-matters","Issue #3: The Stuck Build - Timing Matters",[65,491,68],{"id":492},"what-happened-2",[18,494,495,496,498],{},"Switched back to ",[45,497,74],{}," with hybrid rendering configuration:",[89,500,504],{"className":501,"code":502,"language":503,"meta":97,"style":97},"language-ts shiki shiki-themes github-light github-dark","export default defineNuxtConfig({\n  nitro: {\n    preset: 'vercel',\n  },\n  routeRules: {\n    '/': { prerender: true },\n    '/blog/**': { prerender: true },\n    '/work/**': { prerender: true },\n    '/project/**': { prerender: true },\n    '/api/**': { cors: true },\n  }\n})\n","ts",[45,505,506,522,527,537,542,547,561,572,583,594,607,612],{"__ignoreMap":97},[121,507,508,512,515,519],{"class":123,"line":124},[121,509,511],{"class":510},"szBVR","export",[121,513,514],{"class":510}," default",[121,516,518],{"class":517},"sScJk"," defineNuxtConfig",[121,520,521],{"class":127},"({\n",[121,523,524],{"class":123,"line":131},[121,525,526],{"class":127},"  nitro: {\n",[121,528,529,532,535],{"class":123,"line":141},[121,530,531],{"class":127},"    preset: ",[121,533,534],{"class":150},"'vercel'",[121,536,154],{"class":127},[121,538,539],{"class":123,"line":157},[121,540,541],{"class":127},"  },\n",[121,543,544],{"class":123,"line":170},[121,545,546],{"class":127},"  routeRules: {\n",[121,548,549,552,555,558],{"class":123,"line":183},[121,550,551],{"class":150},"    '/'",[121,553,554],{"class":127},": { prerender: ",[121,556,557],{"class":134},"true",[121,559,560],{"class":127}," },\n",[121,562,563,566,568,570],{"class":123,"line":196},[121,564,565],{"class":150},"    '/blog/**'",[121,567,554],{"class":127},[121,569,557],{"class":134},[121,571,560],{"class":127},[121,573,574,577,579,581],{"class":123,"line":207},[121,575,576],{"class":150},"    '/work/**'",[121,578,554],{"class":127},[121,580,557],{"class":134},[121,582,560],{"class":127},[121,584,585,588,590,592],{"class":123,"line":213},[121,586,587],{"class":150},"    '/project/**'",[121,589,554],{"class":127},[121,591,557],{"class":134},[121,593,560],{"class":127},[121,595,597,600,603,605],{"class":123,"line":596},10,[121,598,599],{"class":150},"    '/api/**'",[121,601,602],{"class":127},": { cors: ",[121,604,557],{"class":134},[121,606,560],{"class":127},[121,608,610],{"class":123,"line":609},11,[121,611,210],{"class":127},[121,613,615],{"class":123,"line":614},12,[121,616,617],{"class":127},"})\n",[18,619,620],{},[28,621,358],{},[22,623,624,629],{},[25,625,363,626],{},[45,627,628],{},"npm run build",[25,630,369,631],{},[45,632,633],{},".output",[18,635,636,637,640],{},"But the build got ",[28,638,639],{},"stuck"," with this prompt:",[89,642,645],{"className":643,"code":644,"language":94},[92],"> postinstall\n> nuxt prepare\n\n[error] [@nuxt/content] Nuxt Content requires `better-sqlite3` module to operate.\n❯ Do you want to install `better-sqlite3` package?\n● Yes / ○ No\n",[45,646,644],{"__ignoreMap":97},[18,648,649],{},"The build hung indefinitely, waiting for user input that couldn't be provided in a CI environment.",[65,651,220],{"id":652},"root-cause-analysis-2",[18,654,655],{},[28,656,657],{},"The lifecycle issue:",[253,659,660,665,675,683,690],{},[25,661,662,663],{},"Vercel runs ",[45,664,243],{},[25,666,667,668,671,672],{},"This triggers the ",[45,669,670],{},"postinstall"," script: ",[45,673,674],{},"nuxt prepare",[25,676,677,678,680,681],{},"During ",[45,679,674],{},", Nuxt Content checks for ",[45,682,111],{},[25,684,685,686,689],{},"Since it's not in ",[45,687,688],{},"dependencies",", Content prompts to install it",[25,691,692],{},"In a non-interactive CI environment, the build hangs forever",[18,694,695],{},[28,696,697],{},"Why the prompt appeared:",[22,699,700,710,713],{},[25,701,702,703,705,706,709],{},"Nuxt Content needs ",[45,704,111],{}," at ",[28,707,708],{},"build time"," to index markdown files",[25,711,712],{},"Even with hybrid rendering, the prerendered routes still need content indexing during the build",[25,714,715],{},"The module wasn't available, triggering the installation prompt",[65,717,719],{"id":718},"final-solution","Final Solution",[18,721,722,725,726,728,729,731,732,735],{},[28,723,724],{},"The key insight:"," ",[45,727,111],{}," is needed at ",[28,730,708],{}," but not at ",[28,733,734],{},"runtime",".",[18,737,738],{},"In npm/Node.js dependency management:",[22,740,741,746],{},[25,742,743,745],{},[45,744,688],{},": Installed in both development and production",[25,747,748,751,752],{},[45,749,750],{},"devDependencies",": Installed only in development and ",[28,753,754],{},"during builds",[18,756,757],{},[28,758,759],{},"The fix:",[89,761,763],{"className":115,"code":762,"language":117,"meta":97,"style":97},"{\n  \"dependencies\": {\n    \"@nuxt/content\": \"^3.11.2\",\n    \"nuxt\": \"^4.3.1\",\n    \"vue\": \"^3.5.28\",\n    \"vue-router\": \"^4.6.4\"\n  },\n  \"devDependencies\": {\n    \"better-sqlite3\": \"^12.6.2\"\n  }\n}\n",[45,764,765,769,775,785,795,805,813,817,824,833,837],{"__ignoreMap":97},[121,766,767],{"class":123,"line":124},[121,768,128],{"class":127},[121,770,771,773],{"class":123,"line":131},[121,772,135],{"class":134},[121,774,138],{"class":127},[121,776,777,779,781,783],{"class":123,"line":141},[121,778,144],{"class":134},[121,780,147],{"class":127},[121,782,151],{"class":150},[121,784,154],{"class":127},[121,786,787,789,791,793],{"class":123,"line":157},[121,788,173],{"class":134},[121,790,147],{"class":127},[121,792,178],{"class":150},[121,794,154],{"class":127},[121,796,797,799,801,803],{"class":123,"line":170},[121,798,186],{"class":134},[121,800,147],{"class":127},[121,802,191],{"class":150},[121,804,154],{"class":127},[121,806,807,809,811],{"class":123,"line":183},[121,808,199],{"class":134},[121,810,147],{"class":127},[121,812,204],{"class":150},[121,814,815],{"class":123,"line":196},[121,816,541],{"class":127},[121,818,819,822],{"class":123,"line":207},[121,820,821],{"class":134},"  \"devDependencies\"",[121,823,138],{"class":127},[121,825,826,828,830],{"class":123,"line":213},[121,827,160],{"class":134},[121,829,147],{"class":127},[121,831,832],{"class":150},"\"^12.6.2\"\n",[121,834,835],{"class":123,"line":596},[121,836,210],{"class":127},[121,838,839],{"class":123,"line":609},[121,840,216],{"class":127},[18,842,843],{},[28,844,845],{},"Why this works:",[253,847,848,875],{},[25,849,850,853,854],{},[28,851,852],{},"Build time"," (Vercel's build machine):",[22,855,856,864,869,872],{},[25,857,858,859,861,862],{},"Installs both ",[45,860,688],{}," and ",[45,863,750],{},[25,865,866,868],{},[45,867,111],{}," is available for Nuxt Content to index files",[25,870,871],{},"Prerendered routes are generated successfully",[25,873,874],{},"No prompts, no hanging",[25,876,877,880,881],{},[28,878,879],{},"Runtime"," (Vercel's Lambda functions):",[22,882,883,889,894,897],{},[25,884,885,886,888],{},"Only ",[45,887,688],{}," are bundled into serverless functions",[25,890,891,893],{},[45,892,111],{}," is not included",[25,895,896],{},"Prerendered pages don't need it (they're just HTML)",[25,898,899],{},"API routes run independently without needing SQLite",[18,901,902,904],{},[28,903,399],{}," ✅ Build completes, content pages load, API routes work.",[10,906],{},[13,908,910],{"id":909},"final-working-configuration","Final Working Configuration",[65,912,914],{"id":913},"packagejson",[45,915,107],{},[89,917,919],{"className":115,"code":918,"language":117,"meta":97,"style":97},"{\n  \"name\": \"portfolio\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"nuxt build\",\n    \"dev\": \"nuxt dev\",\n    \"generate\": \"nuxt generate\",\n    \"preview\": \"nuxt preview\",\n    \"postinstall\": \"nuxt prepare\"\n  },\n  \"dependencies\": {\n    \"@nuxt/content\": \"^3.11.2\",\n    \"nuxt\": \"^4.3.1\",\n    \"vue\": \"^3.5.28\",\n    \"vue-router\": \"^4.6.4\"\n  },\n  \"devDependencies\": {\n    \"better-sqlite3\": \"^12.6.2\"\n  }\n}\n",[45,920,921,925,937,949,960,967,979,991,1003,1015,1025,1029,1035,1046,1057,1068,1077,1082,1089,1098,1103],{"__ignoreMap":97},[121,922,923],{"class":123,"line":124},[121,924,128],{"class":127},[121,926,927,930,932,935],{"class":123,"line":131},[121,928,929],{"class":134},"  \"name\"",[121,931,147],{"class":127},[121,933,934],{"class":150},"\"portfolio\"",[121,936,154],{"class":127},[121,938,939,942,944,947],{"class":123,"line":141},[121,940,941],{"class":134},"  \"type\"",[121,943,147],{"class":127},[121,945,946],{"class":150},"\"module\"",[121,948,154],{"class":127},[121,950,951,954,956,958],{"class":123,"line":157},[121,952,953],{"class":134},"  \"private\"",[121,955,147],{"class":127},[121,957,557],{"class":134},[121,959,154],{"class":127},[121,961,962,965],{"class":123,"line":170},[121,963,964],{"class":134},"  \"scripts\"",[121,966,138],{"class":127},[121,968,969,972,974,977],{"class":123,"line":183},[121,970,971],{"class":134},"    \"build\"",[121,973,147],{"class":127},[121,975,976],{"class":150},"\"nuxt build\"",[121,978,154],{"class":127},[121,980,981,984,986,989],{"class":123,"line":196},[121,982,983],{"class":134},"    \"dev\"",[121,985,147],{"class":127},[121,987,988],{"class":150},"\"nuxt dev\"",[121,990,154],{"class":127},[121,992,993,996,998,1001],{"class":123,"line":207},[121,994,995],{"class":134},"    \"generate\"",[121,997,147],{"class":127},[121,999,1000],{"class":150},"\"nuxt generate\"",[121,1002,154],{"class":127},[121,1004,1005,1008,1010,1013],{"class":123,"line":213},[121,1006,1007],{"class":134},"    \"preview\"",[121,1009,147],{"class":127},[121,1011,1012],{"class":150},"\"nuxt preview\"",[121,1014,154],{"class":127},[121,1016,1017,1020,1022],{"class":123,"line":596},[121,1018,1019],{"class":134},"    \"postinstall\"",[121,1021,147],{"class":127},[121,1023,1024],{"class":150},"\"nuxt prepare\"\n",[121,1026,1027],{"class":123,"line":609},[121,1028,541],{"class":127},[121,1030,1031,1033],{"class":123,"line":614},[121,1032,135],{"class":134},[121,1034,138],{"class":127},[121,1036,1038,1040,1042,1044],{"class":123,"line":1037},13,[121,1039,144],{"class":134},[121,1041,147],{"class":127},[121,1043,151],{"class":150},[121,1045,154],{"class":127},[121,1047,1049,1051,1053,1055],{"class":123,"line":1048},14,[121,1050,173],{"class":134},[121,1052,147],{"class":127},[121,1054,178],{"class":150},[121,1056,154],{"class":127},[121,1058,1060,1062,1064,1066],{"class":123,"line":1059},15,[121,1061,186],{"class":134},[121,1063,147],{"class":127},[121,1065,191],{"class":150},[121,1067,154],{"class":127},[121,1069,1071,1073,1075],{"class":123,"line":1070},16,[121,1072,199],{"class":134},[121,1074,147],{"class":127},[121,1076,204],{"class":150},[121,1078,1080],{"class":123,"line":1079},17,[121,1081,541],{"class":127},[121,1083,1085,1087],{"class":123,"line":1084},18,[121,1086,821],{"class":134},[121,1088,138],{"class":127},[121,1090,1092,1094,1096],{"class":123,"line":1091},19,[121,1093,160],{"class":134},[121,1095,147],{"class":127},[121,1097,832],{"class":150},[121,1099,1101],{"class":123,"line":1100},20,[121,1102,210],{"class":127},[121,1104,1106],{"class":123,"line":1105},21,[121,1107,216],{"class":127},[65,1109,1111],{"id":1110},"nuxtconfigts",[45,1112,1113],{},"nuxt.config.ts",[89,1115,1119],{"className":1116,"code":1117,"language":1118,"meta":97,"style":97},"language-typescript shiki shiki-themes github-light github-dark","export default defineNuxtConfig({\n  modules: ['@nuxt/content'],\n  \n  nitro: {\n    preset: 'vercel',\n  },\n  \n  routeRules: {\n    // Pre-render content pages at build time\n    '/': { prerender: true },\n    '/blog': { prerender: true },\n    '/blog/**': { prerender: true },\n    '/work': { prerender: true },\n    '/work/**': { prerender: true },\n    '/project': { prerender: true },\n    '/project/**': { prerender: true },\n    \n    // Keep API routes as serverless functions\n    '/api/**': { cors: true },\n  }\n})\n","typescript",[45,1120,1121,1131,1142,1147,1151,1159,1163,1167,1171,1177,1187,1198,1208,1219,1229,1240,1250,1255,1260,1270,1274],{"__ignoreMap":97},[121,1122,1123,1125,1127,1129],{"class":123,"line":124},[121,1124,511],{"class":510},[121,1126,514],{"class":510},[121,1128,518],{"class":517},[121,1130,521],{"class":127},[121,1132,1133,1136,1139],{"class":123,"line":131},[121,1134,1135],{"class":127},"  modules: [",[121,1137,1138],{"class":150},"'@nuxt/content'",[121,1140,1141],{"class":127},"],\n",[121,1143,1144],{"class":123,"line":141},[121,1145,1146],{"class":127},"  \n",[121,1148,1149],{"class":123,"line":157},[121,1150,526],{"class":127},[121,1152,1153,1155,1157],{"class":123,"line":170},[121,1154,531],{"class":127},[121,1156,534],{"class":150},[121,1158,154],{"class":127},[121,1160,1161],{"class":123,"line":183},[121,1162,541],{"class":127},[121,1164,1165],{"class":123,"line":196},[121,1166,1146],{"class":127},[121,1168,1169],{"class":123,"line":207},[121,1170,546],{"class":127},[121,1172,1173],{"class":123,"line":213},[121,1174,1176],{"class":1175},"sJ8bj","    // Pre-render content pages at build time\n",[121,1178,1179,1181,1183,1185],{"class":123,"line":596},[121,1180,551],{"class":150},[121,1182,554],{"class":127},[121,1184,557],{"class":134},[121,1186,560],{"class":127},[121,1188,1189,1192,1194,1196],{"class":123,"line":609},[121,1190,1191],{"class":150},"    '/blog'",[121,1193,554],{"class":127},[121,1195,557],{"class":134},[121,1197,560],{"class":127},[121,1199,1200,1202,1204,1206],{"class":123,"line":614},[121,1201,565],{"class":150},[121,1203,554],{"class":127},[121,1205,557],{"class":134},[121,1207,560],{"class":127},[121,1209,1210,1213,1215,1217],{"class":123,"line":1037},[121,1211,1212],{"class":150},"    '/work'",[121,1214,554],{"class":127},[121,1216,557],{"class":134},[121,1218,560],{"class":127},[121,1220,1221,1223,1225,1227],{"class":123,"line":1048},[121,1222,576],{"class":150},[121,1224,554],{"class":127},[121,1226,557],{"class":134},[121,1228,560],{"class":127},[121,1230,1231,1234,1236,1238],{"class":123,"line":1059},[121,1232,1233],{"class":150},"    '/project'",[121,1235,554],{"class":127},[121,1237,557],{"class":134},[121,1239,560],{"class":127},[121,1241,1242,1244,1246,1248],{"class":123,"line":1070},[121,1243,587],{"class":150},[121,1245,554],{"class":127},[121,1247,557],{"class":134},[121,1249,560],{"class":127},[121,1251,1252],{"class":123,"line":1079},[121,1253,1254],{"class":127},"    \n",[121,1256,1257],{"class":123,"line":1084},[121,1258,1259],{"class":1175},"    // Keep API routes as serverless functions\n",[121,1261,1262,1264,1266,1268],{"class":123,"line":1091},[121,1263,599],{"class":150},[121,1265,602],{"class":127},[121,1267,557],{"class":134},[121,1269,560],{"class":127},[121,1271,1272],{"class":123,"line":1100},[121,1273,210],{"class":127},[121,1275,1276],{"class":123,"line":1105},[121,1277,617],{"class":127},[65,1279,1281],{"id":1280},"vercel-settings","Vercel Settings",[22,1283,1284,1291,1299],{},[25,1285,1286,725,1289],{},[28,1287,1288],{},"Build Command:",[45,1290,628],{},[25,1292,1293,725,1296,1298],{},[28,1294,1295],{},"Output Directory:",[45,1297,633],{}," (or leave empty for auto-detection)",[25,1300,1301,1304],{},[28,1302,1303],{},"Node.js Version:"," 18.x or higher",[10,1306],{},[13,1308,1310],{"id":1309},"key-takeaways","Key Takeaways",[65,1312,1314],{"id":1313},"_1-native-modules-are-platform-specific","1. Native Modules Are Platform-Specific",[18,1316,1317,1318,1320],{},"Native Node.js addons like ",[45,1319,111],{}," compile to binaries that only work on specific platforms. Serverless environments like Vercel's Lambda have different runtimes than build machines, causing incompatibility.",[65,1322,1324],{"id":1323},"_2-dependencies-vs-devdependencies-matter-in-serverless","2. Dependencies vs DevDependencies Matter in Serverless",[18,1326,1327],{},"In traditional server deployments, this distinction is minor. In serverless:",[22,1329,1330,1336,1345],{},[25,1331,1332,1335],{},[28,1333,1334],{},"Build environment",": Full access to both dependency types",[25,1337,1338,1341,1342,1344],{},[28,1339,1340],{},"Runtime environment",": Only ",[45,1343,688],{}," are bundled",[25,1346,1347,1348],{},"Build-only tools should be in ",[45,1349,750],{},[65,1351,1353],{"id":1352},"_3-hybrid-rendering-is-the-sweet-spot","3. Hybrid Rendering Is the Sweet Spot",[18,1355,1356],{},"For content sites with API routes:",[22,1358,1359,1362,1365],{},[25,1360,1361],{},"Don't use pure SSR (requires runtime SQLite)",[25,1363,1364],{},"Don't use pure static (loses API routes)",[25,1366,1367],{},"Use hybrid rendering with route rules",[65,1369,1371],{"id":1370},"_4-nuxt-contents-documentation-assumes-context","4. Nuxt Content's Documentation Assumes Context",[18,1373,1374],{},"When the docs say \"no extra config needed for Vercel,\" they assume:",[22,1376,1377,1383,1386],{},[25,1378,1379,1380,1382],{},"You're using ",[45,1381,384],{}," for pure static, OR",[25,1384,1385],{},"You're using hybrid rendering with proper dependency management",[25,1387,1388,1389,1391],{},"They don't mean \"just run ",[45,1390,74],{}," with SQLite in dependencies\"",[65,1393,1395],{"id":1394},"_5-the-build-lifecycle-matters","5. The Build Lifecycle Matters",[18,1397,1398],{},"Understanding when each step runs:",[253,1400,1401,1411,1419,1424,1430],{},[25,1402,1403,1405,1406,1408,1409],{},[45,1404,243],{}," → Installs ",[45,1407,688],{}," + ",[45,1410,750],{},[25,1412,1413,1415,1416,1418],{},[45,1414,670],{}," → Runs ",[45,1417,674],{}," (needs build tools)",[25,1420,1421,1422,1418],{},"Build command → Runs ",[45,1423,74],{},[25,1425,1426,1427,1429],{},"Deploy → Only ",[45,1428,688],{}," packaged",[25,1431,1432],{},"Runtime → Serverless functions execute",[10,1434],{},[13,1436,1438],{"id":1437},"debugging-checklist-for-similar-issues","Debugging Checklist for Similar Issues",[18,1440,1441],{},"If you encounter deployment issues with Nuxt Content on Vercel:",[22,1443,1446,1459,1472,1478,1484,1495,1505,1514],{"className":1444},[1445],"contains-task-list",[25,1447,1450,1455,1456,1458],{"className":1448},[1449],"task-list-item",[1451,1452],"input",{"disabled":1453,"type":1454},true,"checkbox"," Is ",[45,1457,111],{}," in the right place? (devDependencies)",[25,1460,1462,1464,1465,1468,1469,48],{"className":1461},[1449],[1451,1463],{"disabled":1453,"type":1454}," Are you using the correct preset? (",[45,1466,1467],{},"vercel",", not ",[45,1470,1471],{},"vercel-static",[25,1473,1475,1477],{"className":1474},[1449],[1451,1476],{"disabled":1453,"type":1454}," Are content routes prerendered in routeRules?",[25,1479,1481,1483],{"className":1480},[1449],[1451,1482],{"disabled":1453,"type":1454}," Are API routes excluded from prerendering?",[25,1485,1487,1489,1490,1492,1493,48],{"className":1486},[1449],[1451,1488],{"disabled":1453,"type":1454}," Is the build command ",[45,1491,74],{},"? (not ",[45,1494,384],{},[25,1496,1498,1500,1501,1492,1503,48],{"className":1497},[1449],[1451,1499],{"disabled":1453,"type":1454}," Is the output directory ",[45,1502,633],{},[45,1504,372],{},[25,1506,1508,1510,1511,1513],{"className":1507},[1449],[1451,1509],{"disabled":1453,"type":1454}," Did you run ",[45,1512,243],{}," after moving dependencies?",[25,1515,1517,1519],{"className":1516},[1449],[1451,1518],{"disabled":1453,"type":1454}," Are there any broken internal links causing prerender failures?",[10,1521],{},[13,1523,1525],{"id":1524},"alternative-approaches-considered","Alternative Approaches Considered",[65,1527,1529],{"id":1528},"option-a-use-a-different-database-adapter","Option A: Use a Different Database Adapter",[18,1531,1532],{},"Nuxt Content can theoretically use other adapters, but they're not officially documented or recommended. Sticking with the designed workflow is safer.",[65,1534,1536],{"id":1535},"option-b-disable-content-indexing","Option B: Disable Content Indexing",[18,1538,1539],{},"Not viable — it breaks the entire content querying system that Nuxt Content provides.",[65,1541,1543],{"id":1542},"option-c-use-edge-runtime","Option C: Use Edge Runtime",[18,1545,1546],{},"Vercel Edge Runtime doesn't support native modules at all. Would require a complete rearchitecture.",[65,1548,1550],{"id":1549},"option-d-self-host-on-a-traditional-vps","Option D: Self-Host on a Traditional VPS",[18,1552,1553],{},"Would work fine, but defeats the purpose of using Vercel's serverless infrastructure and CDN benefits.",[10,1555],{},[13,1557,1559],{"id":1558},"conclusion","Conclusion",[18,1561,1562],{},"Deploying Nuxt Content v3 on Vercel with API routes requires understanding the intersection of:",[22,1564,1565,1568,1571,1574],{},[25,1566,1567],{},"Native Node.js modules and platform compatibility",[25,1569,1570],{},"Build-time vs runtime dependency management",[25,1572,1573],{},"Hybrid rendering with selective prerendering",[25,1575,1576],{},"Serverless deployment architectures",[18,1578,1579,1580,1582,1583,1585],{},"The final solution is elegant: ",[45,1581,111],{}," in ",[45,1584,750],{},", hybrid rendering with route rules, and the Vercel preset. This gives you:",[22,1587,1588,1591,1594,1597],{},[25,1589,1590],{},"Fast, CDN-served content pages",[25,1592,1593],{},"Working API routes as serverless functions",[25,1595,1596],{},"No native binary issues in production",[25,1598,1599],{},"Automatic redeployment on content changes",[18,1601,1602],{},"The key is letting Nuxt Content do what it's designed to do — handle the database layer intelligently based on context — while providing it the build-time tools it needs without polluting the runtime bundle.",[1604,1605,1606],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":97,"searchDepth":131,"depth":131,"links":1608},[1609,1610,1616,1621,1626,1631,1638,1639,1645],{"id":15,"depth":131,"text":16},{"id":62,"depth":131,"text":63,"children":1611},[1612,1613,1614,1615],{"id":67,"depth":141,"text":68},{"id":100,"depth":141,"text":101},{"id":219,"depth":141,"text":220},{"id":282,"depth":141,"text":283},{"id":405,"depth":131,"text":406,"children":1617},[1618,1619,1620],{"id":409,"depth":141,"text":68},{"id":423,"depth":141,"text":220},{"id":467,"depth":141,"text":468},{"id":488,"depth":131,"text":489,"children":1622},[1623,1624,1625],{"id":492,"depth":141,"text":68},{"id":652,"depth":141,"text":220},{"id":718,"depth":141,"text":719},{"id":909,"depth":131,"text":910,"children":1627},[1628,1629,1630],{"id":913,"depth":141,"text":107},{"id":1110,"depth":141,"text":1113},{"id":1280,"depth":141,"text":1281},{"id":1309,"depth":131,"text":1310,"children":1632},[1633,1634,1635,1636,1637],{"id":1313,"depth":141,"text":1314},{"id":1323,"depth":141,"text":1324},{"id":1352,"depth":141,"text":1353},{"id":1370,"depth":141,"text":1371},{"id":1394,"depth":141,"text":1395},{"id":1437,"depth":131,"text":1438},{"id":1524,"depth":131,"text":1525,"children":1640},[1641,1642,1643,1644],{"id":1528,"depth":141,"text":1529},{"id":1535,"depth":141,"text":1536},{"id":1542,"depth":141,"text":1543},{"id":1549,"depth":141,"text":1550},{"id":1558,"depth":131,"text":1559},"2026-02-21","TL;DR: Deploying a Nuxt 4 portfolio with Nuxt Content v3 and API routes on Vercel requires hybrid rendering with better-sqlite3 in devDependencies. Read on for the complete debugging journey. Yes, this is the story of this website.","md",{},"/blog/deploying-nuxt4-nuxtcontentv3-vercel-with-api-routes",{"title":5,"description":1647},"blog/deploying-nuxt4-nuxtcontentv3-vercel-with-api-routes",[1654,1655,1656,1467,1657],"devlog","nuxt","nuxt content","issue","1_lbGJJ-H52YFATSw7QrC0-AHiNUd7mnNl_lVS_gp30",1771920105785]