DEV Community

Cover image for From Static to Smart: Making a Dynamic Sitemap with Nuxt 3
Alex Marusych
Alex Marusych

Posted on

From Static to Smart: Making a Dynamic Sitemap with Nuxt 3

You don't always need a sitemap, but it can really help. If your site is new, large, or has many pages that aren't well linked, a sitemap makes sure nothing is missed. Sitemaps can also tell search engines how often content changes and what to focus on first. But keep in mind: a sitemap doesn't guarantee indexing. It just increases your chances.

The simplest way to add a sitemap to your Nuxt 3 project is to place a ready-made sitemap.xml file into the public folder. This works fine if your site only has a few pages like the home page, "About Us," and "Contacts." In this case, you don't expect changes in the sitemap, and it stays valid over time.

But what if you're building an e-commerce site? Any page can change at any moment. You may have hundreds of product pages, and their number changes all the time. If you use a static file, you'll need to update it manually every time a product is added, updated, or removed. That's time-consuming and easy to forget.

Luckily, Nuxt 3 makes it easy to create a dynamic sitemap that always stays up to date. You can generate it on the fly using server routes.

Let me show you a real example. I have an online bookstore. All books are stored in a Supabase database. The app has three main pages: the home page, individual book pages, and the order page. The order page isn't relevant for search engines, so I won't include it. Hence, the sitemap should contain only the home page and each book's detail page.

Before we dive into the code, here's what the final version of the sitemap will look like.

<?xml version="1.0" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://best-to-read.com</loc>
  </url>
  <url>
    <loc>https://best-to-read.com/book/the-courage-to-be-disliked-how-to-free-yourself-change-your-life-and-achieve-real-happiness</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
  ,
  <url>
    <loc>https://best-to-read.com/book/the-lean-startup</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
  ,
  <url>
    <loc>https://best-to-read.com/book/art-public-speaking-dale-carnegie</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
</urlset>
Enter fullscreen mode Exit fullscreen mode

It will include a link to the home page, followed by a list of all current books, each with its own URL and last updated date.

To build this, the first step is to create the corresponding server route. The name of the route file should match the URL being requested. So in this case, I name the file sitemap.xml.js. That way, when someone requests /sitemap.xml, Nuxt will automatically use this route to handle the request.

//server/routes/sitemap.xml.js

export default defineEventHandler(async (event) => {

});
Enter fullscreen mode Exit fullscreen mode

Next, I return a basic response from the route.

//server/routes/sitemap.xml.js

export default defineEventHandler(async (event) => {
  const content = `<?xml version="1.0" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://best-to-read.com</loc>
  </url>
  <url>
    <loc>https://best-to-read.com/book/the-courage-to-be-disliked-how-to-free-yourself-change-your-life-and-achieve-real-happiness</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
  ,
  <url>
    <loc>https://best-to-read.com/book/the-lean-startup</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
  ,
  <url>
    <loc>https://best-to-read.com/book/art-public-speaking-dale-carnegie</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
</urlset>
`;

  return content;
});
Enter fullscreen mode Exit fullscreen mode

Sitemap in Nuxt 3. Image 1

At first, it's just plain text. That's why I need to set the response header to let search engines know this is an XML file.

//server/routes/sitemap.xml.js

export default defineEventHandler(async (event) => {
  const content = `<?xml version="1.0" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://best-to-read.com</loc>
  </url>
  <url>
    <loc>https://best-to-read.com/book/the-courage-to-be-disliked-how-to-free-yourself-change-your-life-and-achieve-real-happiness</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
  ,
  <url>
    <loc>https://best-to-read.com/book/the-lean-startup</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
  ,
  <url>
    <loc>https://best-to-read.com/book/art-public-speaking-dale-carnegie</loc>
    <lastmod>2024-10-06</lastmod>
  </url>
</urlset>
`;

  setResponseHeader(event, 'Content-Type', 'application/xml');

  return content;
});
Enter fullscreen mode Exit fullscreen mode

Sitemap in Nuxt 3. Image 2

Now it looks better. All that's left is to replace the hardcoded data with real values. I will load all existing books from the database, replace the hardcoded domain with the actual one from the config, and create a link to each of their pages.

//server/routes/sitemap.xml.js

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig(event);
  const books = await getProducts(event);

  const content = `<?xml version="1.0" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>${config.public.appBaseUrl}</loc>
  </url>

  ${books.map(book => `
  <url>
    <loc>${config.public.appBaseUrl}/book/${book.slug}</loc>
    <lastmod>${getLastmodValue(book.updated_at)}</lastmod>
  </url>
  `)}
</urlset>
`

setResponseHeader(event, 'Content-Type', 'application/xml');
return content;
});
Enter fullscreen mode Exit fullscreen mode

Sitemap in Nuxt 3. Image 3

There are a few helper functions I used. getLastmodValue is a simple function to format a date string.

function getLastmodValue(dateString) {
  return new Date(dateString).toISOString().split('T')[0];
};
Enter fullscreen mode Exit fullscreen mode

getBooks is a function that fetches all books, but only with the necessary properties: slug and updated_at. I use the slug to build the URL for the book's page, and I use the updated_at value to show when the content was last changed. That's important because search engines use this to understand how fresh the page is.

import { createClient } from '@supabase/supabase-js';

async function getBooks(event) {
  const supabaseUrl = process.env.SUPABASE_URL;
  const supabaseKey = process.env.SUPABASE_KEY;
  const client = createClient(supabaseUrl, supabaseKey);

  const { data, error } = await client
    .from('products')
    .select('slug, updated_at');

  if (error) {
    throw createError({
      statusCode: 500,
      message: error.message || 'Something went wrong',
    });
  };

  return data;
}
Enter fullscreen mode Exit fullscreen mode

Of course, this function is just an example and only covers my case. But the main idea stays the same-you need to fetch all the data you need for the sitemap, or return an error.


That's it. I now have a fully dynamic sitemap that updates automatically and helps search engines index my site better. And you have a real example you can use in your own projects. Just adjust it to match your content structure and database.

Top comments (2)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.