Draft: Implement @nuxt/content v3
1. Change Summary
[Describe what is changing, why, and the impact]
- Closes: #342
2. QA Checklist
[This merge request should follow all best practices utilizing our team code standards and our project's development documentation]
-
Code Cleanup: Any messages, linter warnings, and/or deprecation warnings are cleaned up in the console -
Tech Debt: I have created, or documented, any fast follow-up work that needs to be done after this MR is merged -
Efficient Code Review: I have tested and reviewed my own changes thoroughly before assigning a reviewer -
Accessibility: Axe tools run and issues addressed -
Cross-browser compatibility: Works on Safari, Chrome, and Firefox -
Analytics and SEO: Compatible with Google Analytics and SEO tools -
Localization checked for regressions
[Provide concise steps to test the changes]
Review App
Production | Review app |
---|---|
https://about.gitlab.com/ | https://content-v3.about.gitlab-review.app/platform/ |
3. Deployment Steps
[List key deployment steps or configurations]
Merge request reports
Activity
assigned to @ndubord
- Resolved by Nathan Dubord
Changes from Nuxt Content v2 to Content v3 (migration docs):
-
queryContent()
API is replaced with the newqueryCollection().
- The new content API is backed by SQL, and queries happen within their collection.
- Document Driven Mode is now formally supported. It's exactly what we do with our [...slug] file.
- Our collections are defined in
content.config.ts
. - Querying content is significantly faster than before.
Routing strategies:
-
Crawling - By default
nuxt generate
will use Nitro's crawl links to find anchor tags starting with the / route. We generally want to disable this, as we may miss a route that is not referenced, it can also potentially cause issues with localized routes. -
Selective pre-rendering - In
nuxt.config.ts
settingnitro.prerender.routes
to define all our routes. The problem is, how do we ensure our pages collection matches the prerendered routes. This can also be done throughrouteRules
. -
inlineRouteRules - Experimental feature, by enabling the experiment and adding
defineRouteRules({prerender: true})
to a page, you essentially dynamically populate the routeRules config. However, I don't think this works with [...slug], because of when the page is resolved to a route. -
prerender:routes hook - Hooking into
prerender:routes
, you can traverse the filesystem to inject into the ctx.routes. (This is how we currently do it).
How this works:
- [...slug] - At runtime when a route is hit like /platform/, the [...slug] file takes the slug from the URL (/platform/), and then we query the pages collection (content.config.ts) to populate the pages components/content.
-
Pre-rendering - We run
yarn generate
which starts a headless instance of our app, and makes simulated requests to every pre-defined route. The html is then captured and saved as static files which we then host on GCS, and serve through CloudFlare.
Gotchas:
- console.log's inside a nitro hook in /server/plugin files do NOT show up.
Edited by Nathan Dubord -
added 2 commits
- Resolved by Nathan Dubord
@mpenagos-ext Some of the Blog posts are coming back as invalid yml according to Nuxt Content v3. I've identified a troubled post here:
building-a-text-adventure-using-cplusplus-and-code-suggestions
.I'm looking into it now, just wanted to make sure you were aware.
Issue created with Nuxt Content v3
Thread: https://discord.com/channels/1084786508424282154/1344382906558058570
added 27 commits
-
ec5531d5...40cfdc49 - 26 commits from branch
main
- d042ee7d - Merge branch 'main' into content-v3
-
ec5531d5...40cfdc49 - 26 commits from branch
@de_fraz How's your DB knowledge? Content v3 will take our yml files like this Blog Author:
content: name: Nathan Dubord config: headshot: images/blog/headshots/gitlab-logo-extra-whitespace.png config: template: BlogAuthor
and it will create a table for each collection defined in
content.config.ts
sqlite> .table _content_blogAuthors _content_blogTags _content_shared _content_blogCategories _content_info _development_cache _content_blogPosts _content_pages
In this case, the yml file above is a row in the
_content_blogAuthors
table. And the columns are:sqlite> PRAGMA table_info(_content_blogAuthors); 0|id|TEXT|0||1 1|title|VARCHAR|0||0 2|body|TEXT|0||0 3|config|TEXT|0||0 4|content|TEXT|0||0 5|description|VARCHAR|0||0 6|extension|VARCHAR|0||0 7|meta|TEXT|0||0 8|navigation|TEXT|0|true|0 9|path|VARCHAR|0||0 10|seo|TEXT|0|'{}'|0 11|stem|VARCHAR|0||0
Some of these columns are auto-created, and some are from us (config, content, etc.).
As you can see,
config
andcontent
get saved as aTEXT
field, and the value is JSON.sqlite> SELECT content FROM "_content_blogAuthors" LIMIT 1; {"name":"Nathan Dubord","config":{"headshot":"images/blog/headshots/gitlab-logo-extra-whitespace.png"}}
I know this is standard practice, but I'm reading mixed comments online on whether we should do filtering in memory or use sqlite json functions. Example: Fetch all rows that have a content.name that matches either "Nathan Dubord" or "Chris Fraser". I think our data sets are small enough (max 2,000), that fetching everything and then filtering in-memory is the best approach, but wanted to get your thoughts!
Another option is to use the
beforeParse
hook, and injectcontent.name
as something likeauthorName
into the DB. That way it's just a string and we can just do something like.where('authorName', 'IN', 'blogAuthors')
Edited by Nathan DubordFYI @mpenagos-ext
added 3 commits