[{"data":1,"prerenderedAt":851},["ShallowReactive",2],{"blog-list":3},[4,500,691,777],{"id":5,"title":6,"body":7,"date":488,"description":489,"extension":490,"meta":491,"navigation":492,"path":493,"seo":494,"stem":495,"tags":496,"__hash__":499},"blog\u002Fblog\u002Fnuxt-3-seo-checklist.md","A Nuxt 3 SEO Checklist for Developer Portfolios",{"type":8,"value":9,"toc":479},"minimark",[10,14,19,22,90,93,97,104,187,190,194,210,269,276,280,283,346,350,353,417,423,427,468,472,475],[11,12,13],"p",{},"A portfolio can look great and still be invisible to search engines. This is the checklist I use to make a Nuxt 3 site easy for Google to find, crawl and understand.",[15,16,18],"h2",{"id":17},"_1-render-real-html","1. Render real HTML",[11,20,21],{},"Search engines can run JavaScript, but server-rendered or prerendered HTML is indexed faster and more reliably. Nuxt does this by default. For a mostly static site, prerender your routes:",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-ts shiki shiki-themes github-light github-dark","export default defineNuxtConfig({\n  nitro: {\n    prerender: { crawlLinks: true, routes: ['\u002F'] },\n  },\n})\n","ts","",[30,31,32,52,58,78,84],"code",{"__ignoreMap":28},[33,34,37,41,44,48],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"szBVR","export",[33,42,43],{"class":39}," default",[33,45,47],{"class":46},"sScJk"," defineNuxtConfig",[33,49,51],{"class":50},"sVt8B","({\n",[33,53,55],{"class":35,"line":54},2,[33,56,57],{"class":50},"  nitro: {\n",[33,59,61,64,68,71,75],{"class":35,"line":60},3,[33,62,63],{"class":50},"    prerender: { crawlLinks: ",[33,65,67],{"class":66},"sj4cs","true",[33,69,70],{"class":50},", routes: [",[33,72,74],{"class":73},"sZZnC","'\u002F'",[33,76,77],{"class":50},"] },\n",[33,79,81],{"class":35,"line":80},4,[33,82,83],{"class":50},"  },\n",[33,85,87],{"class":35,"line":86},5,[33,88,89],{"class":50},"})\n",[11,91,92],{},"View the page source in your browser. If your headings and text are in the HTML, crawlers can read them.",[15,94,96],{"id":95},"_2-give-every-page-a-unique-title-and-description","2. Give every page a unique title and description",[11,98,99,100,103],{},"Use ",[30,101,102],{},"useSeoMeta"," in each page so no two pages share the same title or description:",[23,105,109],{"className":106,"code":107,"language":108,"meta":28,"style":28},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup>\nuseSeoMeta({\n  title: 'My Page Title',\n  description: 'One or two sentences that describe this page.',\n  ogTitle: 'My Page Title',\n  ogDescription: 'One or two sentences that describe this page.',\n})\n\u003C\u002Fscript>\n","vue",[30,110,111,126,132,143,153,162,172,177],{"__ignoreMap":28},[33,112,113,116,120,123],{"class":35,"line":36},[33,114,115],{"class":50},"\u003C",[33,117,119],{"class":118},"s9eBZ","script",[33,121,122],{"class":46}," setup",[33,124,125],{"class":50},">\n",[33,127,128,130],{"class":35,"line":54},[33,129,102],{"class":46},[33,131,51],{"class":50},[33,133,134,137,140],{"class":35,"line":60},[33,135,136],{"class":50},"  title: ",[33,138,139],{"class":73},"'My Page Title'",[33,141,142],{"class":50},",\n",[33,144,145,148,151],{"class":35,"line":80},[33,146,147],{"class":50},"  description: ",[33,149,150],{"class":73},"'One or two sentences that describe this page.'",[33,152,142],{"class":50},[33,154,155,158,160],{"class":35,"line":86},[33,156,157],{"class":50},"  ogTitle: ",[33,159,139],{"class":73},[33,161,142],{"class":50},[33,163,165,168,170],{"class":35,"line":164},6,[33,166,167],{"class":50},"  ogDescription: ",[33,169,150],{"class":73},[33,171,142],{"class":50},[33,173,175],{"class":35,"line":174},7,[33,176,89],{"class":50},[33,178,180,183,185],{"class":35,"line":179},8,[33,181,182],{"class":50},"\u003C\u002F",[33,184,119],{"class":118},[33,186,125],{"class":50},[11,188,189],{},"Keep titles under about 60 characters and descriptions under about 160, or Google will truncate them.",[15,191,193],{"id":192},"_3-install-the-nuxt-seo-module","3. Install the Nuxt SEO module",[11,195,196,205,206,209],{},[197,198,202],"a",{"href":199,"rel":200},"https:\u002F\u002Fnuxtseo.com",[201],"nofollow",[30,203,204],{},"@nuxtjs\u002Fseo"," bundles a sitemap, robots.txt, canonical URLs and Schema.org helpers. Set your site URL once in ",[30,207,208],{},"nuxt.config.ts",":",[23,211,213],{"className":25,"code":212,"language":27,"meta":28,"style":28},"export default defineNuxtConfig({\n  modules: ['@nuxtjs\u002Fseo'],\n  site: {\n    url: 'https:\u002F\u002Fexample.com',\n    name: 'Your Name',\n  },\n})\n",[30,214,215,225,236,241,251,261,265],{"__ignoreMap":28},[33,216,217,219,221,223],{"class":35,"line":36},[33,218,40],{"class":39},[33,220,43],{"class":39},[33,222,47],{"class":46},[33,224,51],{"class":50},[33,226,227,230,233],{"class":35,"line":54},[33,228,229],{"class":50},"  modules: [",[33,231,232],{"class":73},"'@nuxtjs\u002Fseo'",[33,234,235],{"class":50},"],\n",[33,237,238],{"class":35,"line":60},[33,239,240],{"class":50},"  site: {\n",[33,242,243,246,249],{"class":35,"line":80},[33,244,245],{"class":50},"    url: ",[33,247,248],{"class":73},"'https:\u002F\u002Fexample.com'",[33,250,142],{"class":50},[33,252,253,256,259],{"class":35,"line":86},[33,254,255],{"class":50},"    name: ",[33,257,258],{"class":73},"'Your Name'",[33,260,142],{"class":50},[33,262,263],{"class":35,"line":164},[33,264,83],{"class":50},[33,266,267],{"class":35,"line":174},[33,268,89],{"class":50},[11,270,271,272,275],{},"The ",[30,273,274],{},"site.url"," value is used to build canonical URLs and sitemap entries, so it must match your production domain exactly.",[15,277,279],{"id":278},"_4-keep-preview-deployments-out-of-search-results","4. Keep preview deployments out of search results",[11,281,282],{},"If you deploy to Vercel, preview URLs can get indexed and compete with your real site. Only allow indexing in production:",[23,284,286],{"className":25,"code":285,"language":27,"meta":28,"style":28},"site: {\n  indexable: process.env.VERCEL_ENV\n    ? process.env.VERCEL_ENV === 'production'\n    : process.env.NODE_ENV === 'production',\n}\n",[30,287,288,296,307,324,341],{"__ignoreMap":28},[33,289,290,293],{"class":35,"line":36},[33,291,292],{"class":46},"site",[33,294,295],{"class":50},": {\n",[33,297,298,301,304],{"class":35,"line":54},[33,299,300],{"class":46},"  indexable",[33,302,303],{"class":50},": process.env.",[33,305,306],{"class":66},"VERCEL_ENV\n",[33,308,309,312,315,318,321],{"class":35,"line":60},[33,310,311],{"class":39},"    ?",[33,313,314],{"class":50}," process.env.",[33,316,317],{"class":66},"VERCEL_ENV",[33,319,320],{"class":39}," ===",[33,322,323],{"class":73}," 'production'\n",[33,325,326,329,331,334,336,339],{"class":35,"line":80},[33,327,328],{"class":39},"    :",[33,330,314],{"class":50},[33,332,333],{"class":66},"NODE_ENV",[33,335,320],{"class":39},[33,337,338],{"class":73}," 'production'",[33,340,142],{"class":50},[33,342,343],{"class":35,"line":86},[33,344,345],{"class":50},"}\n",[15,347,349],{"id":348},"_5-add-structured-data","5. Add structured data",[11,351,352],{},"Structured data helps Google connect your site to you as a person or organization. With the Nuxt SEO module you can describe yourself in a few lines:",[23,354,356],{"className":25,"code":355,"language":27,"meta":28,"style":28},"useSchemaOrg([\n  definePerson({\n    name: 'Your Name',\n    jobTitle: 'Frontend Developer',\n    sameAs: ['https:\u002F\u002Fgithub.com\u002Fyou', 'https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fyou\u002F'],\n  }),\n])\n",[30,357,358,366,373,381,391,407,412],{"__ignoreMap":28},[33,359,360,363],{"class":35,"line":36},[33,361,362],{"class":46},"useSchemaOrg",[33,364,365],{"class":50},"([\n",[33,367,368,371],{"class":35,"line":54},[33,369,370],{"class":46},"  definePerson",[33,372,51],{"class":50},[33,374,375,377,379],{"class":35,"line":60},[33,376,255],{"class":50},[33,378,258],{"class":73},[33,380,142],{"class":50},[33,382,383,386,389],{"class":35,"line":80},[33,384,385],{"class":50},"    jobTitle: ",[33,387,388],{"class":73},"'Frontend Developer'",[33,390,142],{"class":50},[33,392,393,396,399,402,405],{"class":35,"line":86},[33,394,395],{"class":50},"    sameAs: [",[33,397,398],{"class":73},"'https:\u002F\u002Fgithub.com\u002Fyou'",[33,400,401],{"class":50},", ",[33,403,404],{"class":73},"'https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fyou\u002F'",[33,406,235],{"class":50},[33,408,409],{"class":35,"line":164},[33,410,411],{"class":50},"  }),\n",[33,413,414],{"class":35,"line":174},[33,415,416],{"class":50},"])\n",[11,418,271,419,422],{},[30,420,421],{},"sameAs"," links tie your GitHub and LinkedIn profiles to your site.",[15,424,426],{"id":425},"_6-submit-your-site-to-search-console","6. Submit your site to Search Console",[428,429,430,440,452,461],"ol",{},[431,432,433,434,439],"li",{},"Add your domain as a property in ",[197,435,438],{"href":436,"rel":437},"https:\u002F\u002Fsearch.google.com\u002Fsearch-console",[201],"Google Search Console"," and verify it with a DNS record.",[431,441,442,443,446,447,451],{},"Submit ",[30,444,445],{},"\u002Fsitemap.xml"," under ",[448,449,450],"strong",{},"Sitemaps",".",[431,453,99,454,457,458,451],{},[448,455,456],{},"URL inspection"," on your homepage and click ",[448,459,460],{},"Request indexing",[431,462,463,464,467],{},"Check the ",[448,465,466],{},"Pages"," report to see why any page is not indexed.",[15,469,471],{"id":470},"_7-build-links-and-content","7. Build links and content",[11,473,474],{},"Technical SEO only makes a site eligible to rank. To actually rank, other sites need to link to you and you need pages worth ranking. Link to your site from your GitHub profile, LinkedIn and project READMEs, and publish pages that answer real questions, one topic per URL.",[476,477,478],"style",{},"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 .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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":28,"searchDepth":54,"depth":54,"links":480},[481,482,483,484,485,486,487],{"id":17,"depth":54,"text":18},{"id":95,"depth":54,"text":96},{"id":192,"depth":54,"text":193},{"id":278,"depth":54,"text":279},{"id":348,"depth":54,"text":349},{"id":425,"depth":54,"text":426},{"id":470,"depth":54,"text":471},"2026-10-03","A practical checklist for getting a Nuxt 3 site indexed by Google, from robots and sitemaps to structured data and Search Console.","md",{},true,"\u002Fblog\u002Fnuxt-3-seo-checklist",{"title":6,"description":489},"blog\u002Fnuxt-3-seo-checklist",[497,498],"nuxt","seo","j5ZaKkkuFnOckZkrPn01OuzcKxGQr0ErIJUCmJqAu_I",{"id":501,"title":502,"body":503,"date":488,"description":683,"extension":490,"meta":684,"navigation":492,"path":685,"seo":686,"stem":687,"tags":688,"__hash__":690},"blog\u002Fblog\u002Fnuxt-prerendering-on-vercel.md","Prerendering a Nuxt 3 Site and Deploying It to Vercel",{"type":8,"value":504,"toc":677},[505,508,512,519,584,591,595,598,616,623,647,650,654,660,664,674],[11,506,507],{},"Prerendering generates HTML for your routes at build time. The browser and search engine crawlers get complete pages immediately, without waiting for JavaScript to run. It works well for portfolios, blogs and documentation, where content rarely changes between deploys.",[15,509,511],{"id":510},"configure-prerendering","Configure prerendering",[11,513,514,515,518],{},"Tell Nitro which routes to render. With ",[30,516,517],{},"crawlLinks"," enabled, Nitro follows internal links from the routes you list and renders those too:",[23,520,522],{"className":25,"code":521,"language":27,"meta":28,"style":28},"export default defineNuxtConfig({\n  nitro: {\n    prerender: {\n      crawlLinks: true,\n      routes: ['\u002F', '\u002Fsitemap.xml', '\u002Frobots.txt'],\n    },\n  },\n})\n",[30,523,524,534,538,543,552,571,576,580],{"__ignoreMap":28},[33,525,526,528,530,532],{"class":35,"line":36},[33,527,40],{"class":39},[33,529,43],{"class":39},[33,531,47],{"class":46},[33,533,51],{"class":50},[33,535,536],{"class":35,"line":54},[33,537,57],{"class":50},[33,539,540],{"class":35,"line":60},[33,541,542],{"class":50},"    prerender: {\n",[33,544,545,548,550],{"class":35,"line":80},[33,546,547],{"class":50},"      crawlLinks: ",[33,549,67],{"class":66},[33,551,142],{"class":50},[33,553,554,557,559,561,564,566,569],{"class":35,"line":86},[33,555,556],{"class":50},"      routes: [",[33,558,74],{"class":73},[33,560,401],{"class":50},[33,562,563],{"class":73},"'\u002Fsitemap.xml'",[33,565,401],{"class":50},[33,567,568],{"class":73},"'\u002Frobots.txt'",[33,570,235],{"class":50},[33,572,573],{"class":35,"line":164},[33,574,575],{"class":50},"    },\n",[33,577,578],{"class":35,"line":174},[33,579,83],{"class":50},[33,581,582],{"class":35,"line":179},[33,583,89],{"class":50},[11,585,586,587,590],{},"Dynamic routes such as ",[30,588,589],{},"\u002Fblog\u002Fmy-post"," are discovered when a prerendered page links to them, so make sure your blog index links to every post.",[15,592,594],{"id":593},"verify-the-output","Verify the output",[11,596,597],{},"Build the site and look at the generated HTML:",[23,599,603],{"className":600,"code":601,"language":602,"meta":28,"style":28},"language-bash shiki shiki-themes github-light github-dark","npx nuxt build\n","bash",[30,604,605],{"__ignoreMap":28},[33,606,607,610,613],{"class":35,"line":36},[33,608,609],{"class":46},"npx",[33,611,612],{"class":73}," nuxt",[33,614,615],{"class":73}," build\n",[11,617,618,619,622],{},"Then request the page with ",[30,620,621],{},"curl"," and check that your content is in the response:",[23,624,626],{"className":600,"code":625,"language":602,"meta":28,"style":28},"curl -s http:\u002F\u002Flocalhost:3000 | grep \"\u003Ch1\"\n",[30,627,628],{"__ignoreMap":28},[33,629,630,632,635,638,641,644],{"class":35,"line":36},[33,631,621],{"class":46},[33,633,634],{"class":66}," -s",[33,636,637],{"class":73}," http:\u002F\u002Flocalhost:3000",[33,639,640],{"class":39}," |",[33,642,643],{"class":46}," grep",[33,645,646],{"class":73}," \"\u003Ch1\"\n",[11,648,649],{},"If the heading shows up, crawlers can see it too.",[15,651,653],{"id":652},"deploy-to-vercel","Deploy to Vercel",[11,655,656,657,451],{},"Vercel detects Nuxt automatically. Push the repository, import it in the Vercel dashboard, and the default build settings work. Add any environment variables, such as API keys for contact forms, under ",[448,658,659],{},"Project Settings → Environment Variables",[15,661,663],{"id":662},"check-what-google-sees","Check what Google sees",[11,665,666,667,669,670,673],{},"After deploying, open Google Search Console, paste a URL into ",[448,668,456],{},", and choose ",[448,671,672],{},"View crawled page",". This shows the HTML Google actually received, so you can confirm that your content and meta tags are present.",[476,675,676],{},"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 .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);}",{"title":28,"searchDepth":54,"depth":54,"links":678},[679,680,681,682],{"id":510,"depth":54,"text":511},{"id":593,"depth":54,"text":594},{"id":652,"depth":54,"text":653},{"id":662,"depth":54,"text":663},"How to prerender a Nuxt 3 site, check that crawlers receive full HTML, and deploy it to Vercel.",{},"\u002Fblog\u002Fnuxt-prerendering-on-vercel",{"title":502,"description":683},"blog\u002Fnuxt-prerendering-on-vercel",[497,689],"vercel","TjUE2lAESftwzOqZaz3nQ2mSTJJwyg9iDNX5DNsuUd0",{"id":692,"title":693,"body":694,"date":768,"description":769,"extension":490,"meta":770,"navigation":492,"path":771,"seo":772,"stem":773,"tags":774,"__hash__":776},"blog\u002Fblog\u002Fnuxt-api-integration.md","Planning the interface between a Nuxt frontend and an API",{"type":8,"value":695,"toc":761},[696,699,703,706,709,713,716,719,723,734,737,741,744,747,751],[11,697,698],{},"My work includes Nuxt applications with Fastify, PostgreSQL, and Prisma, as well as frontend work integrating REST APIs. These are the questions I recommend settling before connecting a screen to its backend.",[15,700,702],{"id":701},"describe-the-screens-data-contract","Describe the screen's data contract",[11,704,705],{},"Start with what the user needs to see and do. For a scheduling screen, that might include a list of events, the currently selected event, and the fields needed to create or edit it. Agree on required fields, nullable values, identifiers, and the format of errors.",[11,707,708],{},"TypeScript types help communicate that contract inside the frontend. They do not validate a network response at runtime. When data comes from outside the application's trust boundary, decide where it will be validated and how invalid responses will be handled.",[15,710,712],{"id":711},"design-more-than-the-successful-response","Design more than the successful response",[11,714,715],{},"A screen needs useful behavior while data is loading, when the result is empty, and when a request fails. An empty list is not the same as an unavailable service. Give each state a clear message and an appropriate next action.",[11,717,718],{},"For writes, think through repeated clicks and retries. Disable duplicate submissions while a request is pending, and work with the backend on safe retry behavior. The user should be able to tell whether their action succeeded before trying it again.",[15,720,722],{"id":721},"choose-where-the-request-should-run","Choose where the request should run",[11,724,725,726,729,730,733],{},"Nuxt can render pages on the server as well as in the browser. For initial page data, its ",[30,727,728],{},"useFetch"," and ",[30,731,732],{},"useAsyncData"," composables support transferring server-fetched data into the client payload. This avoids treating every initial request as a browser-only concern.",[11,735,736],{},"Keep private API credentials in server-only code and configuration. An authenticated screen also needs an explicit plan for how the server obtains the user's session. Never put a secret in public runtime configuration just to make a browser request easier.",[15,738,740],{"id":739},"verify-the-complete-interaction","Verify the complete interaction",[11,742,743],{},"Check a direct page load and navigation from another page. Exercise an empty response, an expired session, a failed request, and a successful mutation. Confirm that updated data appears where the user expects it and that an error does not leave the interface in an ambiguous state.",[11,745,746],{},"These checks connect the API contract to the actual product experience. They are useful whether the backend is Fastify or another service: the frontend still needs to present a reliable account of what happened.",[15,748,750],{"id":749},"further-reading","Further reading",[752,753,754],"ul",{},[431,755,756],{},[197,757,760],{"href":758,"rel":759},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F3.x\u002Fgetting-started\u002Fdata-fetching",[201],"Nuxt documentation: Data fetching",{"title":28,"searchDepth":54,"depth":54,"links":762},[763,764,765,766,767],{"id":701,"depth":54,"text":702},{"id":711,"depth":54,"text":712},{"id":721,"depth":54,"text":722},{"id":739,"depth":54,"text":740},{"id":749,"depth":54,"text":750},"2026-09-27","Practical considerations for Nuxt API integration: data contracts, loading and error states, server rendering, and keeping secrets on the server.",{},"\u002Fblog\u002Fnuxt-api-integration",{"title":693,"description":769},"blog\u002Fnuxt-api-integration",[497,775],"api","02LWelVhondNXN7JcLuqwpgR4Oh8D8aEKayuvt5TDrA",{"id":778,"title":779,"body":780,"date":768,"description":843,"extension":490,"meta":844,"navigation":492,"path":845,"seo":846,"stem":847,"tags":848,"__hash__":850},"blog\u002Fblog\u002Fnuxt-layers-and-shared-packages.md","Nuxt Layers and shared packages: choosing what belongs where",{"type":8,"value":781,"toc":836},[782,785,789,792,795,799,802,805,809,812,815,819,822,825,827],[11,783,784],{},"At X Enterprises, my work includes Nuxt Layers and internal npm packages to share logic and components across websites. These tools solve related problems, but the boundary between them deserves an explicit decision.",[15,786,788],{"id":787},"start-with-what-the-sites-actually-share","Start with what the sites actually share",[11,790,791],{},"Before extracting a shared foundation, list what repeats: application configuration, layout, components, styling, or business logic. Similar-looking pages do not always have the same requirements. A shared implementation is useful when its consumers should evolve together.",[11,793,794],{},"For example, a common navigation layout across a family of sites is a different kind of reuse from a date-formatting utility. The layout depends on the application's conventions; the utility may be useful anywhere.",[15,796,798],{"id":797},"use-a-layer-for-a-shared-nuxt-foundation","Use a layer for a shared Nuxt foundation",[11,800,801],{},"Nuxt Layers let an application extend another Nuxt project. That makes a layer a useful home for shared application configuration and UI conventions. A consuming application can build on that foundation while providing its own content and site-specific behavior.",[11,803,804],{},"Keep the layer's responsibility easy to explain. A layer that contains every exception from every site becomes difficult to change. When two sites need substantially different behavior, consider whether that behavior belongs in each application instead.",[15,806,808],{"id":807},"use-a-package-for-a-defined-reusable-interface","Use a package for a defined reusable interface",[11,810,811],{},"An internal npm package can expose a focused set of utilities, types, or components. The important part is a clear public interface: consumers should know what they can import and which assumptions they must satisfy.",[11,813,814],{},"Layers can also be distributed as packages, so this is not a choice between mutually exclusive delivery methods. The design question is whether the shared code is an application foundation or a focused dependency.",[15,816,818],{"id":817},"make-changes-with-the-consumers-in-view","Make changes with the consumers in view",[11,820,821],{},"For a shared change, identify the sites that consume it and the behavior they depend on. Check representative pages, configuration overrides, and any affected integrations. A build passing in the shared project alone does not establish that every consumer still behaves correctly.",[11,823,824],{},"Write down the reason for the shared boundary. That short explanation is useful when the next feature request arrives: it gives the team a way to decide whether to extend the common code or keep a change local.",[15,826,750],{"id":749},[752,828,829],{},[431,830,831],{},[197,832,835],{"href":833,"rel":834},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F3.x\u002Fguide\u002Fgoing-further\u002Flayers",[201],"Nuxt documentation: Layers",{"title":28,"searchDepth":54,"depth":54,"links":837},[838,839,840,841,842],{"id":787,"depth":54,"text":788},{"id":797,"depth":54,"text":798},{"id":807,"depth":54,"text":808},{"id":817,"depth":54,"text":818},{"id":749,"depth":54,"text":750},"A practical guide to separating shared Nuxt application structure from reusable package logic, informed by multi-site development work.",{},"\u002Fblog\u002Fnuxt-layers-and-shared-packages",{"title":779,"description":843},"blog\u002Fnuxt-layers-and-shared-packages",[497,849],"architecture","hP0nqAmUpuERA0NDa8Z9fI3Y0EtY0LZDslFAxtSdOVw",1791287285133]