Skip to content

Sitemap Extensions

An unofficial extension of the official @astrojs/sitemap integration that allows:

  • Each route to self-declare whether they should be included in the sitemap.
  • Including only a subset of pages rendered from a dynamic route in the sitemap.
  • On-demand (SSR) routes to include themselves in the sitemap, or some of their known pages if they are dynamic.

The goal of this extension is to provide higher-level functionality and configuration over the official integration. These extensions are not maintained nor supported by the Astro team. If some or all of these extensions prove themselves as valuable additions for the users they might be proposed and adopted into the official integration, in that case they would continue to work in this package, but as a thin wrapper or just a re-export from the official package.

Installation

To install this integration you just need to replace the import from the official @astrojs/sitemap integration with @inox-tools/sitemap-ext.

astro.config.mjs
import sitemap from '@astrojs/sitemap';
import sitemap from '@inox-tools/sitemap-ext';
export default defineConfig({
integration: [sitemap()],
});

This integration accepts all the options from @astrojs/sitemap, except for the filter function that is replaced by the per-route configuration.

includeByDefault

Type: boolean Default: false

Whether pre-rendered pages that do not explicitly define their presence in the sitemap should be included.

On-demand routes must always explicitly opt-in to being in the sitemap.

Per-Route configuration

Each of your routes can define how they should be included in your sitemap by importing the module sitemap-ext:config.

Full opt-in/out

For a page to fully opt-in or opt-out of being in the sitemap you can pass a boolean value to the imported module:

---
import sitemap from 'sitemap-ext:config';
sitemap(true); // opt-in
sitemap(false); // opt-out
---

In this case all pages created from the route will follow the given configuration.

Configuration callback

For non-trivial scenarios, you can provide a hook function to define how a route should be included in the sitemap. This hook function will be called during build-time only and will be given callbacks to inform the decisions. It has the following signature:

type ConfigHook = (callbacks: {
addToSitemap: (routeParams?: Record<string, string | undefined>[]) => void;
removeFromSitemap: (routeParams?: Record<string, string | undefined>[]) => void;
setSitemap: (
routeParams: Array<{ sitemap?: boolean; params: Record<string, string | undefined> }>
) => void;
}) => Promise<void> | void;

As noted by the type, the given hook function can be async, which means you can load remote information.

---
import sitemap from 'sitemap-ext:config';
sitemap(async ({ addToSitemap, removeFromSitemap }) => {
const shouldBeIncluded = await fetchThisConfigFromRemote();
if (shouldBeIncluded) {
addToSitemap();
} else {
removeFromSitemap();
}
});
---

Not calling any of the given configuration callbacks means the route will follow what is defined by the global includeByDefault option

addToSitemap / removeFromSitemap

Calling these configuration callbacks without any argument has the same meaning as the full opt in/out using the boolean values.

These functions also allow passing a list of parameter maps in the argument, these parameter maps are the same as the params field in Astro’s getStaticPaths.

On a static route this argument is ignored. On a dynamic route, this argument is used to replace the dynamic segments and add the resulting URLs to the sitemap. This means you can add some pages generated by an Astro route to the sitemap while keeping others out of it.

src/pages/[locale]/[postId].astro
---
import { getCollection } from 'astro:content';
import sitemap from 'sitemap-ext:config';
sitemap(async ({ addToSitemap }) => {
const blogPosts = await getCollection('posts');
addToSitemap(
blogPosts.map((post) => ({
locale: post.data.locale,
postId: post.data.id,
}))
);
});
---

setSitemap

This function receives an array of parameter maps with a boolean to indicate whether each of those parameters describe a route that should be included or excluded from the sitemap.

If the boolean field is not given it defaults to the global includeByDefault option

Calling this function from a static route does nothing.

src/pages/[locale]/[postId].astro
---
import { getCollection } from 'astro:content';
import sitemap from 'sitemap-ext:config';
sitemap(async ({ setSitemap }) => {
const blogPosts = await getCollection('posts');
setSitemap(
blogPosts.map((post) => ({
params: {
locale: post.data.locale,
postId: post.data.id,
},
sitemap: post.data.includeInSitemap,
}))
);
});
---

Interaction with Astro features

getStaticPaths

To avoid writing the same logic twice you can call the sitemap configuration function from inside getStaticPaths. This allows you to compute the params for your routes only once.

src/pages/[locale]/[postId].astro
---
import { getCollection } from 'astro:content';
import sitemap from 'sitemap-ext:config';
export async function getStaticPaths() {
const blogPosts = await getCollection('posts');
const postParams = blogPosts.map((post) => ({
params: {
locale: post.data.locale,
postId: post.data.id,
},
props: { post }, // for Astro.props
sitemap: post.data.includeInSitemap,
}));
sitemap(({ setSitemap }) => setSitemap(postParams));
return postParams;
}
---

On-demand rendered pages (SSR)

The official sitemap integration doesn’t have built-in support for SSR pages, requiring you to re-declare in your configuration all the URLs for server-rendered content that you want included in your sitemap.

Using this extension, on-demand routes can opt into being included in the sitemap. SSR pages on static routes can use the boolean option to add themselves to the sitemap. Dynamic routes can only add the pages that are known at build time. This can be useful in many scenarios:

  • When you can generate the content for any matching URL, but some of them are well-known.
  • When providing self-healing URLs to support renaming your slugs over time.
  • When your content can change per-request, but not the URLs.