Programmatic SEO tutorial: How We Built 2k+ pSEO Pages with BCMS & Nuxt.js

By Arso Stojović, Boris Bošnjak, Momčilo Popov•July 27, 2026•27 min read
When we first started building our programmatic SEO strategy at BCMS, the goal was simple: create scalable landing pages that developers would actually find useful.
Instead of publishing hundreds of generic pages, we wanted to build a system that combined structured data, reusable templates, and high-quality content to target thousands of relevant search queries. Over time, that approach helped us scale our organic presence while keeping content maintainable and consistent.
In this tutorial, I'll walk you through the exact process we used to build our programmatic SEO architecture, from planning your data model and designing page templates to generating landing pages and publishing them with a headless CMS.
If you're new to the concept, I recommend reading our Programmatic SEO Guide first. It explains what programmatic SEO is, when to use it, and why it has become one of the most effective ways to scale organic traffic.
This tutorial focuses on the practical side. By the end, you'll understand how to build a programmatic SEO project from scratch and avoid many of the mistakes we made along the way.
Programmatic SEO at BCMS
We leveraged our expertise in web development, serverless architecture, and headless CMS technology to build meaningful, dynamic landing pages that could scale without sacrificing content quality.
Instead of creating individual pages manually, we developed a reusable system powered by structured content, enabling us to publish thousands of landing pages from a single template.
Initial results
Our first implementation generated more than 2,000 unique landing pages in a relatively short period of time, allowing us to target thousands of long-tail search queries while maintaining a consistent content structure.
We created unique and universal content for more than 2k in a short time to achieve a better ranking on Google. The first pages went live in November 2021 and are now following Google analytics as a data source.
Look at this Programmatic example: for the first 30 days, these pages generated 2880 organic visits. But not only is this generating traffic, but most importantly, it also provides us a glimpse into the niche search trends, helping us prepare better for what's coming.



How to do Programmatic SEO
Building thousands of SEO landing pages manually wasn't a realistic option. We needed a solution that allowed our marketing team to manage content efficiently while giving developers a scalable way to generate and maintain pages.
Using BCMS as a headless CMS and Nuxt.js for the frontend, we built a system that generated more than 2,000 programmatic SEO landing pages from a single reusable template powered by structured content.
The implementation follows a series of steps, from designing the content model to generating and publishing fully rendered landing pages.
Let's start with the foundation: your content model.
Plan your URL structure
A scalable URL structure is just as important as your content model.
Before generating pages, decide on a clean and consistent URL pattern that is easy to maintain as your project grows. Keep URLs short, descriptive, and keyword-focused, while avoiding unnecessary nesting or dates.
This small decision will make routing, indexing, and long-term maintenance much easier.
Keyword research & planning
Before writing any code, we started with keyword research rather than development.
The goal wasn't simply to generate a large number of pages; it was to identify long-tail keywords with consistent search intent that could be grouped into reusable templates. Instead of targeting a single keyword, we built datasets around hundreds of related queries that shared the same page structure.
Modern programmatic SEO tools and even AI can help identify keyword opportunities, estimate search volume, and organize data before importing it into BCMS. This planning phase ensured we were building pages users were actually searching for instead of generating content without purpose.
Programmatic SEO strategies: What We Wanted to Achieve
Goals.
reusable template
dynamic routes
SEO-friendly URLs
API-driven content
non-developers manage content
automatic publishing
Programmatic SEO Architecture Overview
Before writing a single line of code, we needed a workflow that could scale without creating unnecessary maintenance overhead.
Instead of building every landing page manually, we separated content, presentation, and routing into independent layers. This allowed marketers to manage structured content in BCMS while developers focused on reusable templates and application logic.
At a high level, the architecture looked like this:

Each layer has a single responsibility:
BCMS stores structured content such as page titles, descriptions, features, FAQs, images, and other reusable fields.
The BCMS API exposes that content to the frontend.
Nuxt.js generates dynamic routes and renders landing pages using a single reusable template.
Search engines crawl unique, fully rendered pages with optimized metadata and clean URLs.
This separation made it possible to manage thousands of landing pages without duplicating templates or manually updating individual pages. When content changed, editors updated it once in BCMS, and all affected landing pages automatically reflected those changes.
Programmatic SEO tutorial
Step 1: Design your content model in BCMS
The first technical step was deciding how each landing page should be represented inside BCMS.
Since every page followed the same general structure, we created a single reusable content model instead of managing each landing page as a separate custom layout. The goal was to keep the schema flexible enough for marketers to edit content while giving developers predictable data to work with.
Set Up Your BCMS Project
Start by creating a new Programmatic template in BCMS.
While we called ours "Programmatic", you can choose any name that fits your project, such as Locations, Integrations, Comparisons, or Use Cases.
The important part is that every generated landing page uses the same reusable content structure instead of requiring a separate template.
Create a new “Programmatic” template.

Create the “SEO Column” group with the “Words” property of type String Array. A generic name (SEO Column) is being used because we can have as many columns as needed - while we are using it for synonyms and types columns only, there can be a 3rd column for adjectives (blazingly fast, best, well-known, etc.).

Inside of the “Programmatic” template editor, create a new property called “Columns” that is pointing to the previously-created “SEO Column” Group and toggle Array on. This way, we can add our 2 columns of words, one for synonyms and one for types.
Create a single “Programmatic” entry with 2 columns. Add synonyms in the first column and types in the second one. Synonym examples: CMS, Headless CMS, Content Management System…, and types: Marketing Website, Branding Website, Startup Website…
Add text content (widgets, H1,H2,H3,P, etc) and in places where we want to use synonyms, we replace it with {{ synonym }}, same for the type - {{ type }}. E.g., “The best {{ synonym }} for {{ type }},” which in the end can result in “The best CMS for Marketing Websites“ or “The best Content Management System for Branding Websites.”
Check out how it looks in the CMS.
This collection will act as the central content source for every generated landing page. Instead of creating a different content model for each page, all entries should follow the same reusable structure.
At a minimum, each entry needed:
a page title
a URL slug
an SEO title
a meta description
introductory content
reusable content sections
related pages
images
FAQ content
A simplified content model could look like this:
Field | Type | Purpose |
|---|---|---|
| Text | Main page title |
| Text | URL path for the generated page |
| Text | Title used in search results |
| Text | Search result description |
| Rich text | Introductory page content |
| Group or component | Reusable landing page sections |
| Media | Main page image |
| Reference | Internal linking between relevant pages |
| Group or component | Structured FAQ content |
The exact fields will depend on the type of pages you're generating. A location-based project may need fields such as city, country, population, or coordinates, while an integration directory may need product names, categories, descriptions, logos, and feature comparisons.
A structured content model also makes it easier to generate content programmatically while avoiding thin or duplicate content. Instead of storing complete pages, we store reusable content blocks that can be assembled dynamically into high-quality landing pages.
The important part is to avoid storing an entire page as one large rich-text field. Structured fields make the content easier to reuse, validate, filter, and render across thousands of pages.
We also used reference fields to connect related entries. This allowed us to build contextual internal links without hardcoding URLs directly into the Nuxt templates.
Once the content model was defined, every new BCMS entry followed the same schema. That consistency made the API responses predictable and gave us a reliable foundation for dynamic page generation.
Step 2: Populating BCMS with Structured Content
Once the content collection was ready, the next step was populating it with data.
Managing thousands of landing pages manually wasn't realistic, so instead of creating entries one by one, we used the BCMS API to automate the process.
Depending on the project, this structured dataset can come from a database, CSV file, or even Google Sheets before being imported into BCMS. Using automation instead of manual data entry makes it much easier to maintain content as the project grows.
This allowed us to import structured data into BCMS, create new collection items programmatically, and maintain a consistent content structure across every landing page.
Reading through the BCMS API documentation was straightforward. The implementation wasn't very different from the examples provided in the documentation, so we started with the sample code and modified it to fit our content model.
Using the API, we created new collection items automatically instead of entering every page manually.
Reference fields played an important role in the project.
Rather than storing duplicated information inside every landing page, we connected related entries using multi-reference fields. This allowed us to reuse existing content across multiple pages while keeping everything synchronized inside BCMS.
As the dataset grew, importing content through the API became significantly faster and more reliable than maintaining thousands of pages manually.
Instead of editing individual landing pages, content editors only needed to update structured data inside BCMS. Every change was immediately available to the frontend through the API.
Step 3: Building Programmatic SEO Pages with Nuxt.js Dynamic Routes
Once the content was available through the BCMS API, the next step was turning those entries into individual landing pages.
Instead of creating hundreds of static pages, we used Nuxt.js dynamic routing to render every landing page from a single reusable template.
/cms-for-marketing-agencies
Nuxt extracted the page slug, requested the matching content from our API, and rendered it using one reusable template.
Whenever a user requested a page, Nuxt.js resolved the route, fetched the corresponding BCMS entry, and rendered the page using the shared template. This approach allowed us to generate thousands of unique landing pages while maintaining a single codebase.
Generating pages from templates turned out to be the easy part.
Creating the Dynamic Route
The first step on the frontend is creating a dynamic route that can render every generated landing page.
In Nuxt.js, this is done using a single `pages/_slug.vue` file. Every request, such as `/cms-for-marketing-websites`, is handled by the same component.
The component fetches the processed content from the programmatic API, renders the page content, and displays related landing pages.
#lng_ts // pages/_slug.vue <template> <div class="programmaticPage"> // Used to loop over widgets and H1,H2...,P,Ul from CMS content section <ContentManager v-if="content.length > 0" :data="content" /> // Used to show next 6 related articles <ProgrammaticRelated :data="{ items: related }" /> </div> </template> <script lang="ts"> import Vue from "vue"; import ContentManager from "@/components/widgets/ContentManager.vue"; import { BCMSEntryContentParsed } from "@bcms/most/types"; import ProgrammaticRelatedWidget from "@/components/widgets/ProgrammaticRelated.vue"; interface Data { title: string; content: BCMSEntryContentParsed; relatedArticles: Array<{ slug: string; title: string; }>; } export default Vue.extend<Data, unknown, unknown>({ components: { ContentManager, ProgrammaticRelatedWidget, }, async asyncData({ $bcms, params }) { const locale = 'en'; const slug = params.slug; const data: { type: string; content: BCMSEntryContentParsedItem[]; related: Array<{ title: string; subtitle: string; uri: string; }>; } = await $bcms.request({ url: `/${locale}/programmatic/${slug}/data.json`, method: 'get', }); return { title: `CMS for ${data.type}`, content: data.content, related: data.related, }; }, head() { return { title: this.title, }; }, }); </script>
Notice that we're using the `$bcms.request()` method inside `asyncData()` to retrieve the generated landing page before rendering.
Instead of communicating directly with BCMS, the frontend requests a processed response from our programmatic API. This keeps all generation logic centralized while allowing Nuxt.js to render fully populated HTML for every page.
Fetching the Programmatic Page
The heart of this approach is the dynamic page component.
In the original implementation, Nuxt uses asyncData() to fetch the programmatic page before rendering it. This allows both server-side rendering (SSR) and static generation to produce fully rendered HTML that search engines can crawl.
Rendering fully populated HTML improves on-page SEO, helps every search engine understand the page content immediately, and contributes to better SEO performance.
How It Works
Let's break down what happens here:
Nuxt receives the requested URL.
The dynamic route extracts the page slug.
asyncData()runs before the page is rendered.A request is sent to the custom programmatic API.
The returned data becomes available inside the Vue component as
page.
Because everything happens before rendering, search engines receive fully populated HTML instead of an empty page that relies on client-side JavaScript.
Why Use a Custom API?
You might wonder why the page doesn't fetch content directly from BCMS.
The answer is simple: the API does much more than retrieve an entry.
It also:
parses the requested slug,
determines the correct page type,
selects the appropriate synonym,
replaces placeholders throughout the content,
generates related pages,
returns a fully prepared response that Nuxt can render immediately.
Keeping this logic inside the API keeps the frontend simple while centralizing all programmatic SEO logic in one place.
Rendering the Page
Once the request completes, the returned object is assigned to page.
The rest of the component simply renders the content stored in that object, including the page title, description, SEO metadata, body content, and related pages.
This means the Vue component never needs to know how the page was generated, it only needs to display the data it receives.
Fetching the data is only the first step.
The more interesting part happens inside the API, where the requested slug is analyzed, placeholders are replaced with real values, and the final landing page is assembled before being returned to Nuxt.
In the next section, we'll look at how that API works behind the scenes.
Step 4: Building the Programmatic API
The dynamic route is responsible for displaying the page, but it doesn't generate the content itself.
That responsibility belongs to a custom API endpoint. Instead of exposing raw BCMS entries directly to the frontend, we built an intermediate layer that prepares the content before it's rendered.
The API parses the requested URL, retrieves the matching BCMS entry, replaces placeholders, selects content variations, and generates related pages before returning a ready-to-render response.
Parsing the Requested URL
Every generated page follows the same URL structure. For example:
/cms-for-marketing-websites
The API begins by extracting the dynamic part of the URL and validating that it matches the expected format.
const slug = params.slug; const slugParts = slug .split('-for-') .map((e) => e.replace(/-/g, ' ')); if (slugParts.length !== 2) { throw new Error(`Invalid slug "${slug}"`); }
Once the slug is parsed, we know exactly which page type the user requested and can use that information to generate the correct landing page.
Retrieving the Programmatic template
Instead of storing thousands of individual landing pages inside BCMS, we created a single reusable Programmatic entry.
Every generated page starts from this template.
const entry = JSON.parse( JSON.stringify( await bcms.content.entry.findOne( "programmatic", async (e) => e.status !== "draft" && e.meta.en.slug === "programmatic" ) ) ) as ProgrammaticEntry; if (!entry) { throw new Error("Programmatic entry does not exist."); }
This template contains all reusable content blocks, widgets, placeholders, and structured fields shared across every landing page.
Instead of duplicating the same content thousands of times, we only maintain a single template that can generate countless page variations dynamically.
Finding the Correct BCMS Entry
After parsing the slug, the API retrieves the matching programmatic content from BCMS.
Instead of searching for a complete landing page, it looks up the reusable content entry that serves as the foundation for every generated page.
The API then searches the structured dataset for the requested page type.
const type = entry.meta[locale].columns ? entry.meta[locale].columns[1].words.find( e => e.toLowerCase().replace(/-/g, " ") === slugParts[1] ) : ""; if (!type) { throw new Error(`Missing type for "${slugParts[1]}"`); }
If the requested keyword isn't part of the dataset, the API immediately returns an error instead of generating an invalid landing page.
Because all values are stored as structured content inside BCMS, validating requests becomes straightforward.
Why Store Templates Instead of Pages?
This is one of the biggest advantages of using a headless CMS for programmatic SEO. By combining structured content with the requested page type, the API can generate thousands of landing pages from a single reusable template.
Selecting the correct content variation
To avoid generating pages that look completely identical, we also rotate selected content variations.
Using a deterministic seed based on the page slug ensures that every landing page always receives the same variation while still producing different combinations across the entire dataset.
const randomNumber = Seed(slug)(); const synonym = entry.meta[locale].columns ? entry.meta[locale].columns[0].words[ (randomNumber % (entry.meta[locale].columns[0].words.length - 1)) + 1 ] : "CMS";
Because the seed always produces the same value for the same URL, the generated content remains stable across builds without relying on random output.
Why This Architecture Works
Notice that the frontend still knows almost nothing about the generation process.
Nuxt simply asks for:
/api/programmatic/cms-for-marketing-agencies
Everything else happens inside the API.
Keeping the generation logic inside the API allows the frontend to stay focused on rendering the final response while BCMS remains responsible for managing structured content.
Step 5: Replacing Placeholders with Dynamic Content
One of the biggest advantages of using structured content is that the same template can generate thousands of unique landing pages.
Instead of storing separate content for every page, we created reusable templates containing placeholders that are replaced dynamically when a page is requested. This approach dramatically reduces content duplication while keeping the CMS easy to manage.
Writing Reusable Content in BCMS
Inside the Programmatic template, we didn't write page-specific content.
Instead, we used placeholders such as:
The best {{ synonym }} for {{ type }}
Depending on the requested URL, that single sentence could become:
The best CMS for Marketing Websites
or
The best Headless CMS for Startup Websites
This allowed one content entry to power thousands of landing pages without duplicating text throughout the CMS.
Replacing the Placeholders
Once all required values are available, the API replaces every placeholder inside the content before sending it to the frontend.
entry.content[locale] = replaceTypeAndSynonym( entry.content[locale], type, synonym );
Although the function is small, it performs one of the most important jobs in the entire application.
First, it converts the structured content into a JSON string.
That makes it possible to replace every occurrence of the placeholders throughout the content—not just in headings, but also in paragraphs, widgets, CTA sections, or anywhere else they appear.
Once all placeholders have been replaced, the content is converted back into its original object structure and returned to the API.
The result is a fully customized landing page generated from a single reusable template.
Why Use Placeholders?
Without placeholders, editors would need to create and maintain thousands of nearly identical entries.
Instead, they only manage:
reusable headings,
reusable paragraphs,
reusable widgets,
reusable SEO metadata.
The application takes care of replacing the dynamic values before the page is rendered.
This keeps the content model simple while making large-scale content generation practical.
Why This Approach Scales
The beauty of this implementation is that content authors never need to think about individual pages.
They update a single template in BCMS, and every generated landing page automatically reflects those changes.
Whether the site contains 20 pages or 2,000, the editing workflow remains exactly the same.
That's one of the biggest advantages of combining structured content with a headless CMS.
At this stage, the page content has been generated, but one important question remains:
Which synonym should be used?
If a page randomly selected a different synonym on every request, search engines could receive inconsistent content.
To prevent that, the project uses a deterministic seed generator that always produces the same result for the same URL.
In the next section, we'll look at how the Seed() utility keeps every generated page stable while still allowing controlled content variation.
Before and After
Before — content stored in BCMS:
The best {{ synonym }} for {{ type }}
Selected values:
synonym = Headless CMS type = Marketing Websites
After — generated page content:
The best Headless CMS for Marketing Websites

Step 6: Keeping Generated Pages Consistent with Seed()
One challenge with programmatic SEO is introducing variation without making pages unpredictable.
For example, if the page /cms-for-marketing-websites randomly chose a different synonym every time it loaded, search engines could crawl slightly different versions of the same page. That inconsistency can make indexing and caching more difficult.
To avoid this, we used a deterministic seed generator that always produces the same output for the same URL.
Generating a Deterministic Seed
Inside the API, the requested slug is passed to the Seed() utility.
const randomNumber = Seed(slug)(); const rootWord = 'CMS'; const synonym = entry.meta[locale].columns ? entry.meta[locale].columns[0].words[ (randomNumber % (entry.meta[locale].columns[0].words.length - 1)) + 1 ] : 'CMS';
How It Works
Instead of generating a truly random number, the application generates a number based on the page slug.
For example:
cms-for-marketing-websites
will always produce the same numeric seed.
That means every time this page is generated, it will select the same synonym from the configured list.
Another URL, such as:
cms-for-startup-websites
produces a different seed and therefore may select a different synonym.
The result is predictable content variation across thousands of pages.
Why Not Use Math.random()?
Using Math.random() would make every request slightly different.
A page could render as:
The best CMS for Marketing Websites
on one request and:
The best Headless CMS for Marketing Websites
on the next.
Although both versions are valid, serving different content for the same URL is generally undesirable. Stable pages are easier to cache, easier to test, and provide consistent content for users and search engines.
Using a deterministic seed ensures that each URL always renders the same version of the page unless the underlying content changes.
The Seed Utility
The original project implements this using a small hashing utility.
export function Seed(str: string): () => number { let i = 0; let hash = 0; for (hash = 1779033703 ^ str.length; i < str.length; i++) { const bitwise_xor_from_character = hash ^ str.charCodeAt(i); hash = Math.imul(bitwise_xor_from_character, 3432918353); hash = (hash << 13) | (hash >>> 19); } return () => { hash = Math.imul(hash ^ (hash >>> 16), 2246822507); hash = Math.imul(hash ^ (hash >>> 13), 3266489909); return (hash ^= hash >>> 16) >>> 0; }; }
Why This Matters
Although AI can generate multiple content variations, deterministic selection ensures that each URL always renders the same version of the page. This consistency improves caching, testing, and long-term SEO stability.
Notice what this enables.
Suppose your synonym list contains:
CMS
Headless CMS
Content Management System
API-first CMS
The application doesn't rotate through them randomly.
Instead, every page deterministically selects one value based on its slug.
That gives you controlled variation without sacrificing consistency.
It's a small implementation detail, but it makes the entire programmatic SEO system far more predictable and easier to maintain.
At this point, the API has:
identified the requested page,
selected the correct page type,
chosen a consistent synonym,
replaced every placeholder in the content.
The final step is enriching each landing page with related content to strengthen internal linking and improve crawlability. That's where the related pages algorithm comes in.
Step 7: Generating Related Pages
By this point, the landing page has been fully assembled.
The content has been fetched from BCMS, placeholders have been replaced, and a consistent synonym has been selected.
The final step is enriching the page with related landing pages that help both users and search engines discover additional content.
Rather than relying on manually curated links, the project generates these relationships automatically.
Why Related Pages Matter
Strong internal linking improves crawlability, distributes authority across the site, and helps users discover related content.
For programmatic SEO, it's even more important because thousands of landing pages can easily become isolated if they aren't connected together.
Automatically linking related pages helps:
improve navigation,
distribute internal link equity,
increase crawlability,
encourage visitors to continue exploring the site.
Instead of managing these links manually, the application generates them dynamically.
Building the Related Pages List
The original implementation iterates through the available page types and creates a small collection of related landing pages.
const related = []; for (let i = 0; i < 6; i++) { ... related.push({ title, slug, }); }
Skipping the Current Page
One important detail is that the current page is never included in its own related pages section.
The algorithm checks each generated URL before adding it to the collection, ensuring visitors always receive links to different landing pages.
This avoids unnecessary duplication while providing meaningful navigation across the generated content.
Creating Links Dynamically
Each related page is generated from the same structured data already available inside the application.
That means no additional CMS entries need to be created.
Instead, the API combines:
the selected synonym,
the related page type,
the slug format,
to produce a valid landing page URL.
Every generated link follows the same routing pattern used throughout the project, ensuring consistency across the website.
Why Generate Related Pages Automatically?
Imagine maintaining internal links manually across more than 2,000 landing pages.
Every new page would require updating dozens of existing pages to keep the navigation relevant.
By generating related pages programmatically, the application ensures that every landing page automatically participates in the site's internal linking strategy.
As new content is added, the linking structure grows with it—without introducing additional maintenance.
Scaling Internal Linking
One of the biggest advantages of this approach is that internal linking becomes part of the application logic instead of part of the editorial workflow.
Content editors don't have to think about which pages should link together.
They simply manage structured content in BCMS, while the application builds the relationships automatically during page generation.
This keeps both the content model and the editing experience clean, even as the number of landing pages continues to grow.
At this point, the landing page is complete.
The remaining task is making sure every generated URL can be discovered efficiently.
In the next section, we'll look at how the project automatically registers routes and generates static pages during the Nuxt build process.
Step 8: Registering Dynamic Routes
Generating landing pages requires more than simply rendering a dynamic Vue component.
The application also needs to tell Nuxt which routes should exist so they can be generated during the build process.
Rather than maintaining a static list of URLs, the project builds that list automatically from the available programmatic data.
Registering Programmatic Routes
The original project includes a dedicated route configuration responsible for exposing every generated landing page to Nuxt.
Route configuration:
import { createBcmsMostServerRoutes } from "@bcms/most"; import { programmaticApi } from "./api"; export default createBcmsMostServerRoutes({ ...programmaticApi, });
This file acts as the bridge between the structured content stored in BCMS and Nuxt's routing system.
Instead of defining hundreds or thousands of routes manually, the application generates them programmatically.
Why Generate Routes?
For a traditional website, adding a new page usually means creating a new file inside the pages directory.
That approach doesn't scale for programmatic SEO.
Instead, every possible landing page is generated from structured content and registered automatically.
As new content is added to BCMS, the application can expose additional routes without requiring developers to modify the frontend.
Keeping Routing Centralized
Separating route generation into its own file has another important benefit.
Rather than scattering routing logic across multiple components, every generated URL is defined in a single place.
This makes it much easier to:
validate generated URLs,
exclude invalid pages,
control which pages should be published,
extend the routing logic as the project grows.
Keeping routing centralized also makes the implementation easier to understand and maintain over time.
Supporting Static Generation
Once the list of routes has been prepared, Nuxt can iterate through it during the build process and generate a static HTML page for every programmatic landing page.
This combines the flexibility of structured content with the performance benefits of static site generation.
Instead of waiting for pages to be created on demand, every landing page is already available when a visitor—or a search engine crawler—requests it.
Now that Nuxt knows every route that should exist, the final step is generating the static pages during the build process.
In the next section, we'll look at how the project uses nuxt.config.js to automate the generation of thousands of landing pages.
Step 9: Generating Static Pages with Nuxt
At this stage, the application knows how to build every landing page.
The content is stored in BCMS, the API assembles the final response, and Nuxt knows which routes should be generated.
The last step is allowing Nuxt to generate every landing page during the build process.
This ensures that every page is available as fully rendered HTML before a visitor—or a search engine crawler—ever requests it.
Configuring Static Generation
The original project handles this inside the Nuxt configuration.
// nuxt.config.js generate: { async routes() { const routesToGenerate = []; const most = useBcmsMost(); const programmatic = await most.content.entry.findOne( 'programmatic', async () => true, ); programmatic.meta.en.columns[1].words.forEach((word) => { routesToGenerate.push( `/cms-for-${word.toLocaleLowerCase().replace(/ /g, '-')}` ); }); return routesToGenerate; }, },
During the build process, Nuxt executes the routes() function to generate a complete list of programmatic URLs.
Instead of maintaining a hardcoded list of pages, the configuration reads the available page types directly from the Programmatic entry in BCMS and generates a route for each one. As new types are added to the content model, they are automatically included in the next build without requiring any frontend changes.
This approach combines structured content with static site generation, allowing every landing page to be pre-rendered as HTML before deployment. As a result, users receive fast page loads while search engines can immediately crawl fully rendered content.
Why Static Generation?
One of the biggest advantages of combining BCMS with Nuxt is that every landing page can be pre-generated during the build.
Instead of waiting for content to be rendered after a request, each page already exists as static HTML.
This provides several benefits:
faster page loads
improved Core Web Vitals
better SEO
reduced server workload
predictable performance regardless of traffic
Publishing pages at scale doesn't mean sacrificing quality. Static generation allows every landing page to load quickly while following modern SEO best practices.
Building Thousands of Pages Automatically
During the build process, Nuxt iterates through every generated route.
For each route it:
requests the processed content from the programmatic API,
renders the Vue template,
generates a static HTML page,
repeats the process until every landing page has been created.
Because the process is fully automated, adding new programmatic pages usually requires updating structured content rather than writing new frontend code.
The Complete Flow
Looking back, the entire architecture follows a surprisingly simple pipeline.
Keyword Research
│
▼
Structured Dataset
│
▼
BCMS Content Model
│
▼
Programmatic API
│
▼
Placeholder Replacement
│
▼
Related Pages
│
▼
Nuxt Dynamic Route
│
▼
Static Site Generation
│
▼
SEO Landing Pages
Although each individual step is relatively straightforward, combining them creates a scalable workflow capable of generating and maintaining thousands of landing pages from a single reusable content model.
The real strength of this implementation isn't the number of pages it generates.
It's the separation of responsibilities.
BCMS manages structured content.
The API transforms reusable content into page-specific content.
Nuxt renders the final page.
Static generation publishes optimized HTML ready for users and search engines.
Each layer focuses on a single responsibility, making the system easier to maintain as the project grows.
From Prototype to Production
This architecture allowed us to scale beyond manually created landing pages.
Instead of treating every page as an independent document, we built a reusable system where content, routing, and rendering work together.
As the website expanded, the workflow stayed almost exactly the same. Editors continued managing structured content inside BCMS, while developers focused on improving the platform rather than maintaining thousands of individual pages.
That separation is what ultimately made generating and maintaining more than 2,000 programmatic SEO landing pages practical.
Conclusion: Building a Successful Programmatic SEO Workflow
Building more than 2,000 programmatic SEO landing pages wasn't about generating thousands of HTML files—it was about designing a scalable system.
Throughout this tutorial, we've shown how BCMS and Nuxt.js work together to transform structured content into fully rendered landing pages using reusable templates, a custom API, dynamic routing, and static site generation.
Instead of maintaining thousands of individual pages, both developers and content editors work with reusable building blocks that make the entire workflow easier to scale and maintain.
What We Built
By the end of this implementation, we've created a workflow that:
stores reusable content inside BCMS,
generates page-specific content through a custom API,
replaces placeholders dynamically,
selects consistent content variations using a deterministic seed,
automatically creates related pages,
generates Nuxt routes,
builds fully static SEO landing pages.
Every part of the system has a single responsibility, making it easier to extend without increasing complexity.
Explore the Project
If you'd like to explore the implementation in more detail, check out the original project repository and experiment with the architecture yourself.
You can also adapt the same principles to generate other types of structured content—not just SEO landing pages.
Here you can find the GitHub repository with minimal code to reproduce Programmatic SEO functionality with Nuxt.js: https://github.com/becomesco/cms-programmatic-seo-demo
As well as the working demo: https://programmatic-seo-nu.vercel.app/
Need help? Contact us :)
Building a Programmatic SEO Project? Try BCMS
Whether you're building hundreds of landing pages or thousands of structured content pages, BCMS provides the flexibility to model reusable content while Nuxt.js delivers a fast, SEO-friendly frontend.
If you'd like to build a similar system, try BCMS and start experimenting with your own programmatic content workflows.
It takes a minute to start using BCMS
14-day free trial · No credit card required
Content
Ready to build?
Start structuring content with BCMS and scale as your project grows. No credit card needed.
Continue reading

Make better pages with these 30 Programmatic SEO Tools
Boost your Programmatic SEO strategy with the 30 most effective tools. Get ahead of the competition and drive traffic to your site with our guide.

Programmatic SEO examples: the ultimate list with use cases
Programmatic SEO examples such as Nomad Lis, Canva, Zapier, Booking.com show how to start publishing unique, high-quality pages at scale - the right way

Keywords for Programmatic SEO: How to find and rank well
Keywords for Programmatic SEO - How to find and rank well? Find head terms and modifiers and build content around them. This guide shows you how!