
Nuxt websites: How to build dynamic websites in 2026
20 Dec 2023
It takes a minute
Free support
14 days free trial

Many developers still believe that headless content management systems applications are hard to optimize for SEO, and this has discouraged many from adopting modern content management solutions.
This misconception is from the understanding that SEO requires tight coupling (no separation) between the content management and the presentation layers, leading many developers to assume that separating these concerns creates optimization challenges.
Is this really true?
The reality is the direct opposite, or should I say the reality is very far from that claim.
Why is that?
When properly implemented, Nuxt with a headless CMS like BCMS provides high-end SEO control compared to traditional CMSs. You get the flexibility of separate architecture while maintaining full control over how search engines crawl, index, and understand your site.
Headless CMS like BCMS can also be used in mobile applications or even desktop applications, cool right :) Good luck finding a traditional CMS that does that effortlessly.
Now let’s break down the SEO advantages Nuxt has on its own and when combined with a headless CMS.
Search Engines have rapidly evolved over the years and become sophisticated, but even then, the underlying principle remains: they prefer content that is served in its HTML form.
This is where Nuxt’s Static site Generation and SSR come in.
Unlike client-side rendered SPAs that require JavaScript execution to display content, Nuxt delivers fully-formed HTML pages to search engine crawlers. When combined with a headless CMS, you get:
Instant content availability: All text, metadata, and structured data are present in the initial HTML response
Better and faster crawling: Search engines can index your content without waiting for JavaScript execution
Better Core Web Vitals: Pre-rendered content leads to faster loading times and better user experience signals.
💡 Did you know?
Without Nuxt, a single-page application will not be perfectly crawled by a search engine. This is because of how SPAs work. The client(browser) sends a request to the server, and then the server responds with the JavaScript code, which contains just the basic details about the webpage, and then the client converts it to a functional website. By the time it does this, the engine has already finished crawling and moving on, so it only picks up the page title and the basic description you set in the headers, thereby leaving out crucial information.
What is structured content, and how do traditional CMSs fall short?
In traditional CMSs like WordPress, content creation often looks like this:

The problems:
Content creators(who are mostly not developers) forget to fill SEO fields
No validation or requirements on SEO fields
Inconsistent data structure
SEO becomes an afterthought(What do you expect from a writer with no development background)
With a Headless CMS like BCMS, you take a rather defined approach, which is to be respected and obeyed:
// BCMS Content Model Definition { "name": "Blog Post", "fields": [ { "name": "title", "type": "string", "required": true }, { "name": "content", "type": "rich-text", "required": true }, { "name": "seo_meta_title", "type": "string", "required": true, // ← REQUIRED! "validation": { "minLength": 30, "maxLength": 60 }, "helpText": "Optimized title for search engines (30-60 chars)" }, { "name": "seo_meta_description", "type": "string", "required": true, // ← REQUIRED! "validation": { "minLength": 120, "maxLength": 160 } }, { "name": "featured_image", "type": "media", "required": true, "fields": { "alt_text": { "type": "string", "required": true // ← Alt text is mandatory! } } }, { "name": "category", "type": "pointer", "template": "category", "required": true }, { "name": "author", "type": "pointer", "template": "author", "required": true }, { "name": "related_posts", "type": "pointer", "template": "blog_post", "array": true, "maxItems": 3 } ] }
Now, content creators cannot publish without filing all required SEO fields, nor will SEO be an afterthought.
Let’s itemize these benefits.
The problem with traditional CMSs:
// WordPress: Inconsistent metadata $posts = get_posts(); foreach($posts as $post) { $meta_title = get_post_meta($post->ID, 'seo_title', true); // $meta_title might be empty! 😱 $meta_desc = get_post_meta($post->ID, 'seo_description', true); // $meta_desc might be empty too! 😱 // Result: Inconsistent SEO across your site }
// BCMS: Every post GUARANTEED to have complete SEO data const posts = await bcms.entry.getAll({ template: 'blog_post' }); posts.forEach(post => { // These fields are GUARANTEED to exist and be filled: console.log('Title:', post.meta.seo_meta_title); // ✅ Always present console.log('Description:', post.meta.seo_meta_description); // ✅ Always present console.log('Image Alt:', post.meta.featured_image.alt_text); // ✅ Always present console.log('Category:', post.meta.category.title); // ✅ Always present console.log('Author:', post.meta.author.name); // ✅ Always present });
<!-- This template NEVER breaks because data is guaranteed --> <template> <Head> <!-- These will ALWAYS have values --> <Title>{{ post.seo_meta_title }}</Title> <Meta name="description" :content="post.seo_meta_description" /> <Meta property="og:title" :content="post.seo_meta_title" /> <Meta property="og:description" :content="post.seo_meta_description" /> <Meta property="og:image" :content="post.featured_image.url" /> </Head> <article> <h1>{{ post.title }}</h1> <!-- Alt text is guaranteed to exist --> <img :src="post.featured_image.url" :alt="post.featured_image.alt_text" /> <div v-html="post.content"></div> </article> </template>
60% of posts are missing meta descriptions
Inconsistent internal linking
No structured data
Manual SEO optimisation (often forgotten)
100% complete SEO metadata (enforced by content model)
Systematic internal linking strategy
Automatic rich snippets generation
SEO becomes part of the content creation process
// SEO audit results comparison const seoAudit = { before: { pages_with_meta_description: '60%', pages_with_structured_data: '5%', internal_links_per_page: 2.3, rich_snippets_eligible: '15%' }, after: { pages_with_meta_description: '100%', // ✅ Enforced by content model pages_with_structured_data: '100%', // ✅ Auto-generated internal_links_per_page: 8.7, // ✅ Systematic relationships rich_snippets_eligible: '85%' // ✅ Structured data } }
Now let’s talk about BCMS and see how integrating a headless CMS like BCMS gives you all you need to build a fully SEO performant Nuxt website.
BCMS provides powerful features that aid SEO content modelling. These are:
Templates: Templates are the building blocks of your content in BCMS. Templates in BCMS define the content structure. And then from these structures, you create entries that are dynamic contents.
Here’s what a template looks like:

From the picture above, you notice the fields and their property types. These are what a template is made out of. Now, on this template, you’ll notice the SEO field, which is called a group pointer. This leads us to our second BCMS feature for integrating SEO.
Groups: Groups in BCMS are basically a collection of properties.
In other words, groups are reusable building blocks that can be integrated into templates and other groups. So you can create an SEO group and model all SEO content that you want there, for example, the meta title, the OG image, the meta description, and a lot more SEO fields you will need for your website.

The image above is a representation of what a simple SEO group looks like. Of course, you can expand it further by adding more properties as you deem fit.
Now, if you connect this BCMS instance to your Nuxt project, you’ll get predefined types that are defined from the SEO properties you created in the group.

Having known the power of using a headless CMS like BCMS to achieve SEO superiority, let’s dive in again to Nuxt to see some of its features for performant SEO.
Nuxt 3 comes packed with SEO optimization features that work seamlessly with headless CMS architectures.
// nuxt.config.ts export default defineNuxtConfig({ nitro: { prerender: { routes: ['/sitemap.xml', '/robots.txt'] } }, ssr: true, // Enable SSR by default // Route-specific rendering rules routeRules: { '/blog/**': { ssr: true }, // Dynamic blog posts with SSR '/': { prerender: true }, // Static homepage '/admin/**': { ssr: false }, // SPA mode for admin areas } })
The Nuxt ecosystem provides powerful SEO-focused modules:
@nuxtjs/seo @nuxtjs/sitemap @nuxt/image
NuxtjsSEO: Nuxt.js SEO is termed the complete SEO solution for Nuxt. It contains 6 fully equipped technical SEO modules for developers and saves you a lot of development time. The 6 modules are
🤖 @nuxtjs/robots - Manage site crawling
📄 @nuxtjs/sitemap - Sitemap.xml Support
🔎 nuxt-schema-org - Generate Schema.org JSON-LD for SEO
△ nuxt-seo-utils - Experimental SEO meta features
🖼️ nuxt-og-image - Generate dynamic social share images
✅ nuxt-link-checker - Check for broken links
NuxtjsSitemap: Nuxt Sitemap is a module for generating best-practice XML sitemaps that are consumed by the robots crawling your site.
NuxtImage: Plug-and-play image optimization for Nuxt apps. Resize and transform your images using the built-in optimiser or your favourite images CDN.
Let’s use these modules together for optimal SEO:
// nuxt.config.ts export default defineNuxtConfig({ modules: [ '@nuxtjs/seo', '@nuxt/image', '@nuxtjs/sitemap' ], seo: { // Global SEO defaults meta: [ { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { name: 'theme-color', content: '#ffffff' } ] }, image: { // Optimize images automatically format: ['webp', 'png', 'jpg'], quality: 80, densities: [1, 2], domains: ['your-bcms-domain.com'] }, sitemap: { hostname: 'https://yoursite.com', gzip: true, routes: async () => { // Dynamically generate sitemap from BCMS content const posts = await $fetch('/api/blog-posts') return posts.map(post => ({ url: `/blog/${post.slug}`, lastmod: post.updatedAt, priority: 0.8 })) } } })
Nuxt's composable system makes it easy to manage SEO metadata dynamically:
//pages/blog/[slug].vue <script setup> const route = useRoute() const slug = route.params.slug // Fetch blog post data from BCMS const { data: post } = await useAsyncData(`blog-${slug}`, () => $fetch(`/api/blog/${slug}`) ) if (!post.value) { throw createError({ statusCode: 404, statusMessage: 'Blog post not found' }) } const title = post.value.seo?.meta_title; const description = post.value.seo?.meta_description; // Dynamic SEO metadata useHead({ title: title || post.value.title, meta: [ { name: 'description', content: description || post.value.excerpt?.slice(0, 160) }, { property: 'og:title', content: title || post.value.title }, { property: 'og:description', content: description || post.value.excerpt }, { property: 'og:image', content: post.value.seo?.og_image?.url || post.value.featured_image?.url }, { property: 'og:type', content: 'article' }, { name: 'twitter:card', content: 'summary_large_image' } ], link: [ { rel: 'canonical', href: post.value.seo?.canonical_url || `https://yoursite.com/blog/${post.value.slug}` } ] }) // Structured data for blog posts useJsonld({ '@context': 'https://schema.org', '@type': 'BlogPosting', headline: post.value.title, description: post.value.excerpt, author: { '@type': 'Person', name: post.value.author.name }, datePublished: post.value.publishedAt, dateModified: post.value.updatedAt, image: post.value.featured_image?.url }) </script>
Read more about search engine crawling here: Harlan WiltonControlling Web Crawlers in Vue & Nuxt · Nuxt SEO
Now, let’s discuss performance optimization strategies for Nuxt SEO
This section will have a look at advanced image handling, caching strategies, and content delivery optimizations.
Combine Nuxt Image with BCMS media optimization:
//vue // Image optimization with @nuxt/image and BCMS <template> <div> <NuxtImg :src="post.featuredImage.src" :alt="post.featuredImage.alt" width="800" height="400" format="webp" quality="80" loading="lazy" placeholder sizes="sm:100vw md:50vw lg:400px" /> </div> </template>
// server/api/blog/[slug].get.js const { data, error } = await useAsyncData(`opening-${route.params.slug}`, async () => { const opening = (await $bcms.entry.get({ template: 'opening', entry: route.params.slug })) as OpeningEntry; //This is an automatic type generated by BCMS from the properties in an entry return opening; }); const opening = data.value;
The caching occurring in the code above is a simple reactive key, which in this case is opening-${route.params.slug}. The response from this function is automatically cached until the key value, which is the slug, changes, and then the data is refetched for that new value
You can also optimize your content so it can be delivered effectively using the Nitro engine, which your Nuxt application runs on:
// nuxt.config.ts export default defineNuxtConfig({ experimental: { payloadExtraction: false // Reduce bundle size }, nitro: { compressPublicAssets: true, minify: true }, css: ['~/assets/css/main.css'], vite: { css: { preprocessorOptions: { scss: { additionalData: '@use "~/assets/scss/global.scss" as *;' } } }, optimizeDeps: { include: ['@becomes/cms-client'], //You can add your dependencies here }, } })
That was a lot of technicalities scattered all over the place, let’s put all this knowledge together and build a fully SEO optimized Blog application using Nuxt and BCMS.
To do this, I will use a blog starter application from BCMS. You can find all BCMS starters here
To get started with the blog application, copy this command and run on your terminal:
npx @thebcms/cli create nuxt starter blogThis command will:
Connect your terminal instance to your BCMS account(create one if you don’t have one) by logging in.
Initialize a new Nuxt project on your computer
Populate that project with the Nuxt blog starter content
Create templates and entries in your BCMS dashboard.
Follow the instructions on your terminal to finish the setup, and your project will be available locally:

Project Walkthrough
If your login to your BCMS dashboard at app.thebcms.com, you’ll find your instance with the name you set up when creating the project, on mine it is Nuxtblog.
Navigate to templates on the left side of the administration pane, and you’ll see all the templates that have been created.

Click on the Blog template and you’ll find all the properties, along with the SEO group pointer. I will change the required attribute of the SEO group pointer to true, so it is always enforced for every blog post.

Now, on the Groups tab, you should see the SEO group. For now, it contains just Title and description, which is not all you need for the SEO of a blog post. So I’m gonna create these additional fields:
Keywords - string array - required
Og image - media - required
Og image alit - string - required
You could also add OG images for different social media platforms, but I won’t. I will simply use one image for all social media platforms.

Now, click on entries on the administration tab, click on the blog Entries and edit all SEO fields adding the newly updated properties.
And then on the blog/[slug].vue page, I will console log the data so I can inspect and retrieve the SEO data I need to use for the page.
It is located at meta.seo:

With this information, I can use all the properties in the useHead composable:
const title = post.value.seo?.meta_title; const description = post.value.seo?.meta_description; const og_image.url = data.value.meta.seo.og_image.url; const og_image_alt = data.value.meta.seo.og_image_alt; useHead(() => ({ title: title, meta: [ { name: 'keywords', content: data.value.meta.seo.keywords.join(', ') }, { property: 'og:image', content: og_image.url }, { property: 'og:title', content: title }, { property: 'og:description', content: description }, { property: 'og:type', content: 'article' }, { property: 'og:url', content: data.value.meta.seo.url }, { property: 'og:site_name', content: data.value.meta.seo.site_name }, { name: 'twitter:title', content: title }, { name: 'twitter:description', description }, { name: 'twitter:url', content: data.value.meta.seo.url }, { name: 'twitter:image', content: og_image.url }, { name: 'twitter:image:alt', content: og_image_alt }, { name: 'fb:image', content: og_image.url }, { name: 'fb:image:alt', content:og_image_alt }, { property: 'og:image:alt', content: og_image_alt }, { property: 'twitter:image', content: og_image.url }, { property: 'twitter:image:alt', content: og_image_alt }, { property: 'fb:image', content: og_image.url }, { property: 'fb:image:alt', content: og_image_alt } ] }));
With this, I have successfully used all the needed properties so this app can show useful information on social media sites and also the information needed for SEO performance.
You can take this application further by installing the nuxtseo module, configuring your sitemaps and playing with the nuxt robots module.
The combination of BCMS and Nuxt creates a powerful, flexible foundation for building SEO-optimized websites that can compete with any traditional CMS solution. By leveraging structured content modeling, server-side rendering, and modern performance optimization techniques, you get the best of both worlds: developer flexibility and search engine visibility.
The key advantages of this approach include:
Complete control over HTML output and SEO implementation
Structured content that ensures consistency across all pages
Performance optimization through modern web technologies
Scalable architecture that grows with your content needs
Future-proof development using cutting-edge frameworks and practices
The days of compromising between developer experience and SEO performance are over. Modern headless CMS architectures, when properly implemented with frameworks like Nuxt, provide superior SEO capabilities compared to traditional monolithic systems.
Don't let outdated misconceptions hold your projects back. The toolchain of BCMS + Nuxt offers everything you need to build fast, SEO-optimized websites that rank well and provide excellent user experiences.
Ready to get started? Try implementing these techniques in a real project. Start with the BCMS Nuxt blog starter, implement the SEO strategies outlined in this guide, and watch your search engine visibility improve. The modern web rewards sites that prioritize both developer experience and user experience, and this stack delivers on both fronts.
Get all the latest BCMS updates, news and events.
By submitting this form you consent to us emailing you occasionally about our products and services. You can unsubscribe from emails at any time, and we will never pass your email to third parties.
There are many actionable insights in this blog post. Learn more: