[{"data":1,"prerenderedAt":922},["ShallowReactive",2],{"page-/post/nuxt/nuxt-content-v3-use-migrate":3,"surrounding-page":913},{"id":4,"title":5,"author":6,"body":7,"date":900,"description":5,"extension":901,"group":6,"lastmod":902,"meta":903,"navigation":279,"path":904,"rawbody":905,"seo":906,"showTitle":6,"stem":907,"tags":908,"versions":910,"__hash__":912},"content/post/nuxt/nuxt-content-v3-use-migrate.md","nuxt-content v3 使用及迁移记录",null,{"type":8,"value":9,"toc":898},"minimark",[10,22,25,32,43,54,73,92,95,114,238,249,256,439,450,457,463,538,544,561,570,580,590,597,671,680,689,699,702,705,720,725,807,810,819,824,827,832,835,840,851,854,864,882,885,888,891,894],[11,12,13,14,18,19,21],"p",{},"这是一篇关于 ",[15,16,17],"code",{},"nuxt/content"," v3 版本的使用和迁移指南，分享一下我在使用 ",[15,20,17],{}," 时的一些经验和问题。",[11,23,24],{},"此前，我基于 v2 版本完成了博客内文章的渲染，在发布了 v3 版本后，我第一时间更新到了最新的版本。",[11,26,27],{},[28,29],"img",{"alt":30,"src":31},"","https://img.zzao.club/article/202412261015007.png",[11,33,34,35,42],{},"在之前的文章",[36,37,41],"a",{"href":38,"rel":39},"https://zzao.club/post/nuxt/nuxt3-obsidian-build-your-blog",[40],"nofollow","《基于 Nuxt3 + Obsidian 搭建个人博客》"," 中，我已经分享了全部配置以及用法。",[11,44,45,46,49,50,53],{},"大概思路是使用 ",[15,47,48],{},"Obsidian"," 在本地来管理的文件，在发布时在本地打包，将打包后的 ",[15,51,52],{},".output"," 文件部署到服务器上。这样完全没有破坏以前的写文章路径，同时通过一个简单的插件给文章页面加上了一个复制到公众号的选项，做到了在公众号和个人博客站展示一致。",[11,55,56,57,60,61,64,65,68,69,72],{},"之前存在一个问题，解析非英文路径时，会把非英文的部分直接丢弃掉（或识别不出来），在 v2 中解决办法是在 ",[15,58,59],{},"server/plugins"," 中增加一个 ",[15,62,63],{},"hook"," ： ",[15,66,67],{},"content:file:beforeParse"," ，手动去处理文件的原始内容，插入一个 ",[15,70,71],{},"_path"," 属性，以此保留了原始的中文路径，算是一个十分膈应的解决方式。",[11,74,75,76,80,81,85,86,88,89],{},"但 v3 版本是破坏性升级，核心的部分也已经被重构了，",[77,78,79],"del",{},"所以 v2 使用的 hook 也不存在了。"," 更正：文档突然冒出来的，真的！还有两个 ",[36,82,63],{"href":83,"rel":84},"https://content3.nuxt.dev/docs/advanced/hooks",[40],"  ",[15,87,67],{}," 、",[15,90,91],{},"content:file:afterParse",[11,93,94],{},"虽然还有，但是 v3 版本已经不需要使用此方式解决！",[11,96,97,98,103,104,109,110,113],{},"我在 ",[36,99,102],{"href":100,"rel":101},"https://github.com/nuxt/content/issues/2889",[40],"issues#2889"," 提出了这个问题后，很快也得到了 ",[36,105,108],{"href":106,"rel":107},"https://github.com/nuxt/content/pull/2898",[40],"pull#2889"," 解决，所以你如果使用的是 ",[15,111,112],{},"v3.0.0-alpha.8"," 及以后的版本，想必都是可以的。",[115,116,120],"pre",{"className":117,"code":118,"language":119,"meta":30,"style":30},"language-typescript shiki shiki-themes github-light","export default defineNuxtConfig({\n  content: {\n    build: {\n      // 虽然官方没有写 markdown: {} ，实际 ts 提示这个配置是必须的\n      markdown: {},\n      pathMeta: {\n        slugifyOptions: {\n          // Keep everything except invalid chars, this will preserve Chinese characters \n          remove: /[$*+~()'\"!\\-=#?:@]/g,\n        }\n      }\n    }\n  }\n})\n","typescript",[15,121,122,142,148,154,161,167,173,179,185,208,214,220,226,232],{"__ignoreMap":30},[123,124,127,131,134,138],"span",{"class":125,"line":126},"line",1,[123,128,130],{"class":129},"sD7c4","export",[123,132,133],{"class":129}," default",[123,135,137],{"class":136},"s7eDp"," defineNuxtConfig",[123,139,141],{"class":140},"sgsFI","({\n",[123,143,145],{"class":125,"line":144},2,[123,146,147],{"class":140},"  content: {\n",[123,149,151],{"class":125,"line":150},3,[123,152,153],{"class":140},"    build: {\n",[123,155,157],{"class":125,"line":156},4,[123,158,160],{"class":159},"sAwPA","      // 虽然官方没有写 markdown: {} ，实际 ts 提示这个配置是必须的\n",[123,162,164],{"class":125,"line":163},5,[123,165,166],{"class":140},"      markdown: {},\n",[123,168,170],{"class":125,"line":169},6,[123,171,172],{"class":140},"      pathMeta: {\n",[123,174,176],{"class":125,"line":175},7,[123,177,178],{"class":140},"        slugifyOptions: {\n",[123,180,182],{"class":125,"line":181},8,[123,183,184],{"class":159},"          // Keep everything except invalid chars, this will preserve Chinese characters \n",[123,186,188,191,195,199,202,205],{"class":125,"line":187},9,[123,189,190],{"class":140},"          remove:",[123,192,194],{"class":193},"sYBdl"," /",[123,196,198],{"class":197},"sYu0t","[$*+~()'\"!\\-=#?:@]",[123,200,201],{"class":193},"/",[123,203,204],{"class":129},"g",[123,206,207],{"class":140},",\n",[123,209,211],{"class":125,"line":210},10,[123,212,213],{"class":140},"        }\n",[123,215,217],{"class":125,"line":216},11,[123,218,219],{"class":140},"      }\n",[123,221,223],{"class":125,"line":222},12,[123,224,225],{"class":140},"    }\n",[123,227,229],{"class":125,"line":228},13,[123,230,231],{"class":140},"  }\n",[123,233,235],{"class":125,"line":234},14,[123,236,237],{"class":140},"})\n",[11,239,240,241,244,245,248],{},"另外 ",[15,242,243],{},"v3.0.0-alpha.7"," 连内容搜索时的分页 api 都没支持好，所以最起码也要用 ",[15,246,247],{},"alpha.8"," 及以后的版本了。",[11,250,251,252,255],{},"配置层面，现在拓展性更强了一些，并且提升到了一个新的 ",[15,253,254],{},"content.config.ts"," 中",[115,257,259],{"className":117,"code":258,"language":119,"meta":30,"style":30},"import { defineCollection, z } from '@nuxt/content'\n\nexport const collections = {\n  content: defineCollection({\n    // 表示文章和目录是一对一的关系，一个文件会生成一个路由\n    type: 'page',\n    source: {\n      include: '**/*.md',\n      exclude: ['**/-*.md'],\n      // 设置读取content的根目录\n      cwd: '/Users/aatrox/notion/blog',\n    },\n    schema: z.object({\n      date: z.date(),\n      tags: z.array(z.string()),\n      versions: z.array(z.string()),\n    })\n  })\n}\n\n",[15,260,261,275,281,297,307,312,322,327,337,348,353,363,368,378,389,407,421,427,433],{"__ignoreMap":30},[123,262,263,266,269,272],{"class":125,"line":126},[123,264,265],{"class":129},"import",[123,267,268],{"class":140}," { defineCollection, z } ",[123,270,271],{"class":129},"from",[123,273,274],{"class":193}," '@nuxt/content'\n",[123,276,277],{"class":125,"line":144},[123,278,280],{"emptyLinePlaceholder":279},true,"\n",[123,282,283,285,288,291,294],{"class":125,"line":150},[123,284,130],{"class":129},[123,286,287],{"class":129}," const",[123,289,290],{"class":197}," collections",[123,292,293],{"class":129}," =",[123,295,296],{"class":140}," {\n",[123,298,299,302,305],{"class":125,"line":156},[123,300,301],{"class":140},"  content: ",[123,303,304],{"class":136},"defineCollection",[123,306,141],{"class":140},[123,308,309],{"class":125,"line":163},[123,310,311],{"class":159},"    // 表示文章和目录是一对一的关系，一个文件会生成一个路由\n",[123,313,314,317,320],{"class":125,"line":169},[123,315,316],{"class":140},"    type: ",[123,318,319],{"class":193},"'page'",[123,321,207],{"class":140},[123,323,324],{"class":125,"line":175},[123,325,326],{"class":140},"    source: {\n",[123,328,329,332,335],{"class":125,"line":181},[123,330,331],{"class":140},"      include: ",[123,333,334],{"class":193},"'**/*.md'",[123,336,207],{"class":140},[123,338,339,342,345],{"class":125,"line":187},[123,340,341],{"class":140},"      exclude: [",[123,343,344],{"class":193},"'**/-*.md'",[123,346,347],{"class":140},"],\n",[123,349,350],{"class":125,"line":210},[123,351,352],{"class":159},"      // 设置读取content的根目录\n",[123,354,355,358,361],{"class":125,"line":216},[123,356,357],{"class":140},"      cwd: ",[123,359,360],{"class":193},"'/Users/aatrox/notion/blog'",[123,362,207],{"class":140},[123,364,365],{"class":125,"line":222},[123,366,367],{"class":140},"    },\n",[123,369,370,373,376],{"class":125,"line":228},[123,371,372],{"class":140},"    schema: z.",[123,374,375],{"class":136},"object",[123,377,141],{"class":140},[123,379,380,383,386],{"class":125,"line":234},[123,381,382],{"class":140},"      date: z.",[123,384,385],{"class":136},"date",[123,387,388],{"class":140},"(),\n",[123,390,392,395,398,401,404],{"class":125,"line":391},15,[123,393,394],{"class":140},"      tags: z.",[123,396,397],{"class":136},"array",[123,399,400],{"class":140},"(z.",[123,402,403],{"class":136},"string",[123,405,406],{"class":140},"()),\n",[123,408,410,413,415,417,419],{"class":125,"line":409},16,[123,411,412],{"class":140},"      versions: z.",[123,414,397],{"class":136},[123,416,400],{"class":140},[123,418,403],{"class":136},[123,420,406],{"class":140},[123,422,424],{"class":125,"line":423},17,[123,425,426],{"class":140},"    })\n",[123,428,430],{"class":125,"line":429},18,[123,431,432],{"class":140},"  })\n",[123,434,436],{"class":125,"line":435},19,[123,437,438],{"class":140},"}\n",[11,440,441,442,445,446,449],{},"包含哪些文件，忽略哪些文件可以自由配置，新增的 ",[15,443,444],{},"front matter"," 属性，如 ",[15,447,448],{},"versions","，也可以自行添加。",[11,451,452,453,456],{},"v2 版本时，content 是在运行时把文件存储在 cache 目录下。而 v3 版本彻底抛弃了 file 模式，直接转为了 ",[15,454,455],{},"sqlite"," 存储，所以相对应的，内容相关的查询和渲染 API 也都进行了修改",[11,458,459,462],{},[15,460,461],{},"queryCollection"," 的替换",[115,464,466],{"className":117,"code":465,"language":119,"meta":30,"style":30},"// 分页查询\nqueryCollection('content').skip(skip).limit(10).all()\n// 查数量\ncount.value = await queryCollection('content').count()\n",[15,467,468,473,508,513],{"__ignoreMap":30},[123,469,470],{"class":125,"line":126},[123,471,472],{"class":159},"// 分页查询\n",[123,474,475,477,480,483,486,489,492,495,497,500,502,505],{"class":125,"line":144},[123,476,461],{"class":136},[123,478,479],{"class":140},"(",[123,481,482],{"class":193},"'content'",[123,484,485],{"class":140},").",[123,487,488],{"class":136},"skip",[123,490,491],{"class":140},"(skip).",[123,493,494],{"class":136},"limit",[123,496,479],{"class":140},[123,498,499],{"class":197},"10",[123,501,485],{"class":140},[123,503,504],{"class":136},"all",[123,506,507],{"class":140},"()\n",[123,509,510],{"class":125,"line":150},[123,511,512],{"class":159},"// 查数量\n",[123,514,515,518,521,524,527,529,531,533,536],{"class":125,"line":156},[123,516,517],{"class":140},"count.value ",[123,519,520],{"class":129},"=",[123,522,523],{"class":129}," await",[123,525,526],{"class":136}," queryCollection",[123,528,479],{"class":140},[123,530,482],{"class":193},[123,532,485],{"class":140},[123,534,535],{"class":136},"count",[123,537,507],{"class":140},[11,539,540,543],{},[15,541,542],{},"useContent()"," 被移除了。",[11,545,546,549,550,549,553,556,557,560],{},[15,547,548],{},"\u003CContentDoc>",", ",[15,551,552],{},"\u003CContentList>",[15,554,555],{},"\u003CContentNavigation>"," and ",[15,558,559],{},"\u003CContentQuery>"," 这个三个组件也被移除",[11,562,563,566,567],{},[15,564,565],{},"fetchContentNavigation()"," API 被替换成了新的 ",[15,568,569],{},"queryCollectionNavigation()",[11,571,572,573,576,577],{},"markdown 文本中的 ",[15,574,575],{},"._path"," 被改为了 ",[15,578,579],{},".path",[11,581,582,583,586,587],{},"一些和旧版 content 配套使用的 ",[15,584,585],{},"sitemap"," 逻辑也应该删除 ",[15,588,589],{},"/server/routes/sitemap.ts",[11,591,592,593,596],{},"当在 ",[15,594,595],{},"pages/xxx/[...slug].vue"," 渲染文章时：",[115,598,600],{"className":117,"code":599,"language":119,"meta":30,"style":30},"const { data: page } = await useAsyncData(route.path, () => {\n    return queryCollection('content').path(decodeURI(route.path)).first()\n  })\n",[15,601,602,638,667],{"__ignoreMap":30},[123,603,604,607,610,614,617,620,623,625,627,630,633,636],{"class":125,"line":126},[123,605,606],{"class":129},"const",[123,608,609],{"class":140}," { ",[123,611,613],{"class":612},"sqxcx","data",[123,615,616],{"class":140},": ",[123,618,619],{"class":197},"page",[123,621,622],{"class":140}," } ",[123,624,520],{"class":129},[123,626,523],{"class":129},[123,628,629],{"class":136}," useAsyncData",[123,631,632],{"class":140},"(route.path, () ",[123,634,635],{"class":129},"=>",[123,637,296],{"class":140},[123,639,640,643,645,647,649,651,654,656,659,662,665],{"class":125,"line":144},[123,641,642],{"class":129},"    return",[123,644,526],{"class":136},[123,646,479],{"class":140},[123,648,482],{"class":193},[123,650,485],{"class":140},[123,652,653],{"class":136},"path",[123,655,479],{"class":140},[123,657,658],{"class":136},"decodeURI",[123,660,661],{"class":140},"(route.path)).",[123,663,664],{"class":136},"first",[123,666,507],{"class":140},[123,668,669],{"class":125,"line":150},[123,670,432],{"class":140},[11,672,673],{},[674,675,676,677,679],"strong",{},"需要使用 ",[15,678,658],{}," 才能在数据库中匹配到对应的文章。因为路径中包含中文",[11,681,682,683,688],{},"更详细的迁移说明，可以移步",[36,684,687],{"href":685,"rel":686},"https://content3.nuxt.dev/docs/getting-started/migration",[40],"官方迁移文档","。（或者等我想起来，会更新在博客站中）",[11,690,691,692,695,696,698],{},"现在使用 v3 开发模式时，会在根目录下创建一个",[15,693,694],{},".data"," 目录，然后生成一个 ",[15,697,455],{}," 文件用于存储文章内容。",[11,700,701],{},"并且这个数据库不需要你来管理，只要配置好内容来源和规则。",[11,703,704],{},"修改为最新版的 api 后，我又增加了一项改动，把从本地文件获取文章改为了从 github 仓库获取。",[11,706,707,708,711,712,715,716,719],{},"原因是，以前我在公司和家里用的同一个笔记本，没有感知到什么问题。现在家里添置了一个新的 ",[15,709,710],{},"macmini"," ，我在家里配置好了同样的环境后，两边带着个 ",[15,713,714],{},"output"," 文件在 ",[15,717,718],{},"git"," 上太诡异了，而且本地的路径也不一致。",[11,721,722,723],{},"也就是需要修改一下 ",[15,724,254],{},[115,726,728],{"className":117,"code":727,"language":119,"meta":30,"style":30},"source: {\n      include: 'blog/**/*.md',\n      exclude: ['blog/**/-*.md'],\n      prefix: '/post',\n      // cwd: process.env.CONTENT_FS_PATH,\n      repository: 'https://github.com/aatrooox/xxxxx',\n      authToken: process.env.CONTENT_REPO_TOKEN\n    },\n",[15,729,730,738,750,763,775,780,792,803],{"__ignoreMap":30},[123,731,732,735],{"class":125,"line":126},[123,733,734],{"class":136},"source",[123,736,737],{"class":140},": {\n",[123,739,740,743,745,748],{"class":125,"line":144},[123,741,742],{"class":136},"      include",[123,744,616],{"class":140},[123,746,747],{"class":193},"'blog/**/*.md'",[123,749,207],{"class":140},[123,751,752,755,758,761],{"class":125,"line":150},[123,753,754],{"class":136},"      exclude",[123,756,757],{"class":140},": [",[123,759,760],{"class":193},"'blog/**/-*.md'",[123,762,347],{"class":140},[123,764,765,768,770,773],{"class":125,"line":156},[123,766,767],{"class":136},"      prefix",[123,769,616],{"class":140},[123,771,772],{"class":193},"'/post'",[123,774,207],{"class":140},[123,776,777],{"class":125,"line":163},[123,778,779],{"class":159},"      // cwd: process.env.CONTENT_FS_PATH,\n",[123,781,782,785,787,790],{"class":125,"line":169},[123,783,784],{"class":136},"      repository",[123,786,616],{"class":140},[123,788,789],{"class":193},"'https://github.com/aatrooox/xxxxx'",[123,791,207],{"class":140},[123,793,794,797,800],{"class":125,"line":175},[123,795,796],{"class":136},"      authToken",[123,798,799],{"class":140},": process.env.",[123,801,802],{"class":197},"CONTENT_REPO_TOKEN\n",[123,804,805],{"class":125,"line":181},[123,806,367],{"class":140},[11,808,809],{},"配置上自己的仓库地址，以及对应的 authToken.",[11,811,812],{},[674,813,814,815,818],{},"authToken 的配置在 github => 点击我的头像 => settings => 左侧最下方 ",[15,816,817],{},"developer settings"," => Personal access tokens => fine-grained tokens",[11,820,821],{},[28,822],{"alt":30,"src":823},"https://img.zzao.club/article/202412261015008.png",[11,825,826],{},"为某一个库生成 token，同时不要忘记给他放开 read-only 权限",[11,828,829],{},[28,830],{"alt":30,"src":831},"https://img.zzao.club/article/202412261015009.png",[11,833,834],{},"最后生成是可以看到有哪些权限",[11,836,837],{},[28,838],{"alt":30,"src":839},"https://img.zzao.club/article/202412261015010.png",[11,841,842,843,846,847,850],{},"如果你生成了 ",[15,844,845],{},"token","，但是没给权限，最后使用 ",[15,848,849],{},"content"," 运行时报的错压根不会让你想到是 token 的权限问题。别问我怎么知道的。",[11,852,853],{},"如果本地文件比较多的，建议给本地的知识库划分一下仓库。一是 obsidian 自己如果文件太多，也会有性能问题，二是 nuxt/content 要去拉取你的代码，太多了拉的也慢。三是划分好后，哪些文件时对外展示的也比较清晰。",[11,855,856,857,859,860,863],{},"配置 ",[15,858,849],{}," 的 ",[15,861,862],{},"repo"," 时，就配这个需要对外生成文章的仓库就好了。",[11,865,866,867,870,871,873,874,877,878,881],{},"当运行 ",[15,868,869],{},"dev"," 时，刚才提到的本地数据库目录 ",[15,872,694],{},"，下会多出一个 ",[15,875,876],{},"github-aatrooox-xxx-main","，里面就是所有原始的文件。当应用执行第一个查询检索内容时，就会读取上一步已经生成的 ",[15,879,880],{},"Dump"," 恢复到目标数据库中，同时有一套检查机制确保数据库中为最新的内容和避免重复导入。在客户端导航时，会在浏览器中初始化本地 SQLite，初始化后所有查询就是在本地进行了，所以 v3 的查询要比 v2 感觉上快很多。",[11,883,884],{},"以上就是 v3 的使用指南，基于官方的迁移说明，并且还解决了一些中文互联网才会有问题。",[11,886,887],{},"另外现在最新版处于不稳定状态，建议观望一下再升级，并且 nuxt/content 在网络上热度不高，出现奇怪的问题都没地儿找答案。",[11,889,890],{},"如果你恰好也是 Nuxt 的使用者，欢迎关注我，一起来讨论。",[11,892,893],{},"👋👋",[895,896,897],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}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 pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":30,"searchDepth":144,"depth":144,"links":899},[],"2024-12-06T00:00:00.000Z","md","2025-08-19T00:00:00.000Z",{},"/post/nuxt/nuxt-content-v3-use-migrate","---\ndate: 2024-12-06\nlastmod: 2025-08-19\ntitle: nuxt-content v3 使用及迁移记录\ntags: [\"Nuxt\"]\nversions: [\"@nuxt/content@3.0.0-alpha.8\"]\ndescription: nuxt-content v3 使用及迁移记录\n---\n这是一篇关于 `nuxt/content` v3 版本的使用和迁移指南，分享一下我在使用 `nuxt/content` 时的一些经验和问题。\n\n此前，我基于 v2 版本完成了博客内文章的渲染，在发布了 v3 版本后，我第一时间更新到了最新的版本。\n\n![](https://img.zzao.club/article/202412261015007.png)\n\n在之前的文章[《基于 Nuxt3 + Obsidian 搭建个人博客》](https://zzao.club/post/nuxt/nuxt3-obsidian-build-your-blog) 中，我已经分享了全部配置以及用法。\n\n大概思路是使用 `Obsidian` 在本地来管理的文件，在发布时在本地打包，将打包后的 `.output` 文件部署到服务器上。这样完全没有破坏以前的写文章路径，同时通过一个简单的插件给文章页面加上了一个复制到公众号的选项，做到了在公众号和个人博客站展示一致。\n\n之前存在一个问题，解析非英文路径时，会把非英文的部分直接丢弃掉（或识别不出来），在 v2 中解决办法是在 `server/plugins` 中增加一个 `hook` ： `content:file:beforeParse` ，手动去处理文件的原始内容，插入一个 `_path` 属性，以此保留了原始的中文路径，算是一个十分膈应的解决方式。\n\n但 v3 版本是破坏性升级，核心的部分也已经被重构了，~~所以 v2 使用的 hook 也不存在了。~~ 更正：文档突然冒出来的，真的！还有两个 [hook](https://content3.nuxt.dev/docs/advanced/hooks)  `content:file:beforeParse` 、`content:file:afterParse`\n\n虽然还有，但是 v3 版本已经不需要使用此方式解决！\n\n我在 [issues#2889](https://github.com/nuxt/content/issues/2889) 提出了这个问题后，很快也得到了 [pull#2889](https://github.com/nuxt/content/pull/2898) 解决，所以你如果使用的是 `v3.0.0-alpha.8` 及以后的版本，想必都是可以的。\n\n```typescript\nexport default defineNuxtConfig({\n  content: {\n    build: {\n      // 虽然官方没有写 markdown: {} ，实际 ts 提示这个配置是必须的\n\t  markdown: {},\n      pathMeta: {\n        slugifyOptions: {\n          // Keep everything except invalid chars, this will preserve Chinese characters \n          remove: /[$*+~()'\"!\\-=#?:@]/g,\n        }\n      }\n    }\n  }\n})\n```\n\n另外 `v3.0.0-alpha.7` 连内容搜索时的分页 api 都没支持好，所以最起码也要用 `alpha.8` 及以后的版本了。\n\n配置层面，现在拓展性更强了一些，并且提升到了一个新的 `content.config.ts` 中\n\n```typescript\nimport { defineCollection, z } from '@nuxt/content'\n\nexport const collections = {\n  content: defineCollection({\n\t// 表示文章和目录是一对一的关系，一个文件会生成一个路由\n    type: 'page',\n    source: {\n      include: '**/*.md',\n      exclude: ['**/-*.md'],\n      // 设置读取content的根目录\n      cwd: '/Users/aatrox/notion/blog',\n    },\n    schema: z.object({\n      date: z.date(),\n      tags: z.array(z.string()),\n      versions: z.array(z.string()),\n    })\n  })\n}\n\n```\n\n包含哪些文件，忽略哪些文件可以自由配置，新增的 `front matter` 属性，如 `versions`，也可以自行添加。\n\nv2 版本时，content 是在运行时把文件存储在 cache 目录下。而 v3 版本彻底抛弃了 file 模式，直接转为了 `sqlite` 存储，所以相对应的，内容相关的查询和渲染 API 也都进行了修改\n\n`queryCollection` 的替换\n\n```typescript\n// 分页查询\nqueryCollection('content').skip(skip).limit(10).all()\n// 查数量\ncount.value = await queryCollection('content').count()\n```\n\n`useContent()` 被移除了。\n\n`\u003CContentDoc>`, `\u003CContentList>`, `\u003CContentNavigation>` and `\u003CContentQuery>` 这个三个组件也被移除\n\n`fetchContentNavigation()` API 被替换成了新的 `queryCollectionNavigation()`\n\nmarkdown 文本中的 `._path` 被改为了 `.path` \n\n一些和旧版 content 配套使用的 `sitemap` 逻辑也应该删除 `/server/routes/sitemap.ts`\n\n当在 `pages/xxx/[...slug].vue` 渲染文章时：\n\n```typescript\nconst { data: page } = await useAsyncData(route.path, () => {\n    return queryCollection('content').path(decodeURI(route.path)).first()\n  })\n```\n\n**需要使用 `decodeURI` 才能在数据库中匹配到对应的文章。因为路径中包含中文**\n\n更详细的迁移说明，可以移步[官方迁移文档](https://content3.nuxt.dev/docs/getting-started/migration)。（或者等我想起来，会更新在博客站中）\n\n现在使用 v3 开发模式时，会在根目录下创建一个`.data` 目录，然后生成一个 `sqlite` 文件用于存储文章内容。\n\n并且这个数据库不需要你来管理，只要配置好内容来源和规则。\n\n修改为最新版的 api 后，我又增加了一项改动，把从本地文件获取文章改为了从 github 仓库获取。\n\n原因是，以前我在公司和家里用的同一个笔记本，没有感知到什么问题。现在家里添置了一个新的 `macmini` ，我在家里配置好了同样的环境后，两边带着个 `output` 文件在 `git` 上太诡异了，而且本地的路径也不一致。\n\n也就是需要修改一下 `content.config.ts`\n\n```typescript\nsource: {\n      include: 'blog/**/*.md',\n      exclude: ['blog/**/-*.md'],\n      prefix: '/post',\n      // cwd: process.env.CONTENT_FS_PATH,\n      repository: 'https://github.com/aatrooox/xxxxx',\n      authToken: process.env.CONTENT_REPO_TOKEN\n    },\n```\n\n配置上自己的仓库地址，以及对应的 authToken.\n\n**authToken 的配置在 github => 点击我的头像 => settings => 左侧最下方 `developer settings` => Personal access tokens => fine-grained tokens** \n\n![](https://img.zzao.club/article/202412261015008.png)\n\n为某一个库生成 token，同时不要忘记给他放开 read-only 权限\n\n![](https://img.zzao.club/article/202412261015009.png)\n\n最后生成是可以看到有哪些权限\n\n![](https://img.zzao.club/article/202412261015010.png)\n\n如果你生成了 `token`，但是没给权限，最后使用 `content` 运行时报的错压根不会让你想到是 token 的权限问题。别问我怎么知道的。\n\n如果本地文件比较多的，建议给本地的知识库划分一下仓库。一是 obsidian 自己如果文件太多，也会有性能问题，二是 nuxt/content 要去拉取你的代码，太多了拉的也慢。三是划分好后，哪些文件时对外展示的也比较清晰。\n\n配置 `content` 的 `repo` 时，就配这个需要对外生成文章的仓库就好了。\n\n当运行 `dev` 时，刚才提到的本地数据库目录 `.data`，下会多出一个 `github-aatrooox-xxx-main`，里面就是所有原始的文件。当应用执行第一个查询检索内容时，就会读取上一步已经生成的 `Dump` 恢复到目标数据库中，同时有一套检查机制确保数据库中为最新的内容和避免重复导入。在客户端导航时，会在浏览器中初始化本地 SQLite，初始化后所有查询就是在本地进行了，所以 v3 的查询要比 v2 感觉上快很多。\n\n以上就是 v3 的使用指南，基于官方的迁移说明，并且还解决了一些中文互联网才会有问题。\n\n另外现在最新版处于不稳定状态，建议观望一下再升级，并且 nuxt/content 在网络上热度不高，出现奇怪的问题都没地儿找答案。\n\n如果你恰好也是 Nuxt 的使用者，欢迎关注我，一起来讨论。\n\n👋👋\n\n",{"title":5,"description":5},"post/nuxt/nuxt-content-v3-use-migrate",[909],"Nuxt",[911],"@nuxt/content@3.0.0-alpha.8","bcRU3Nr3d3SjWccyKnJGvnQ5_SxqZlKxeefJEJlZdwc",[914,918],{"title":915,"path":916,"stem":917},"OpenClaw 安装入门（Windows）","/post/zzao/openclaw/openclaw-install-windows","post/zzao/openclaw/openclaw-install-windows",{"title":919,"path":920,"stem":921},"假设你是AI，你的Skill应该是什么样的","/post/zzao/ai-skill-structure","post/zzao/ai-skill-structure",1779005086473]