Skip to main content
Version: 3.x

Customize the sitemap

Since version 3.4

Introduction

This guide provides step-by-step instructions on how to customize the sitemap for your Front-Commerce application. Customizing your sitemap can help improve SEO by ensuring search engines can easily crawl and index your site's content.

Static Pages Customization

getSitemapEntries Function

To customize the sitemap for static pages, use the getSitemapEntries function in the SEO handle. This function allows you to add custom URLs, set the last modification date, change frequency, and priority for each URL.

Example:

./extensions/acme/routes/blog.tsx
import { createSeoHandle } from "@front-commerce/remix/seo";

export const handle = createSeoHandle({
getSitemapEntries: ({ request, app }) => [
{
url: "/blog",
lastmod: new Date(),
changefreq: "daily",
priority: 0.5,
images: ["/blog-image.jpg"],
},
],
});
caution

Ensure server-only logic in getSitemapEntries is implemented using vite-env-only to prevent leaking into client-side code.

Dynamic Pages Customization

Dynamic pages can also be customized using getSitemapEntries or a custom sitemapFetcher.

getSitemapEntries Function

./extensions/acme/routes/todo.$id.tsx
import { createSeoHandle } from "@front-commerce/remix/seo";
import { serverOnly$ } from "vite-env-only";

export const handle = createSeoHandle({
getSitemapEntries: serverOnly$(async ({ request, app }) => {
const posts = await fetch(
"https://jsonplaceholder.typicode.com/posts"
).then((res) => res.json());

return posts.map((post) => ({
url: `/todo/${post.id}`,
}));
}),
});

sitemapFetcher Option

Refer to Registering Sitemap Fetcher for custom fetcher registration.

./extensions/acme/routes/todo.$slug.tsx
import { createSeoHandle } from "@front-commerce/remix/seo";

export const handle = createSeoHandle({
sitemapFetcher: "todoComposition",
});

Creating and Registering Custom Fetchers

Before creating new fetchers let's dive into the SitemapService service to get a better understanding of how it works.

The SitemapService is composed of two main parts:

  • Composite: A list of fetchers that are executed in parallel to generate the sitemap.
  • FetcherLeaf: A function that returns an array of sitemap entries.

Here is a diagram to help you visualize the Sitemap Service:

visualize-sitemap-service

As you can see, each service is able to register it's own fetchers for a composition, this will help with customisation later on in the guide.

Register a composition

Compositions are generally registered by the extensions which are responsible for adding the handle in the routes.

In theme chocolatine, we have already have a few compositions (products, category, cms).

You can register your own composition in your extension definition:

./extensions/acme/index.ts
import { createSitemapFetcher } from "@front-commerce/core";

export default defineExtension({
name: "acme",
meta: import.meta,
unstable_lifecycleHooks: {
onServerServicesInit: async (services, request, config) => {
services.Sitemap.registerComposition("acmeComposition");
},
},
});

Registering a Fetcher

First we will learn how to register a fetcher, the next section will cover how to create a fetcher.

Registering a fetcher is similar to registering a composition, you can do it in your extension definition.

tip

If you register a fetcher to a non-existent composition, the composition will be added automatically.

./extensions/acme/index.ts
import { defineExtension } from "@front-commerce/core";

export default defineExtension({
name: "acme",
meta: import.meta,
unstable_lifecycleHooks: {
onServerServicesInit: async (services, request, config) => {
services.Sitemap.registerFetcher("todoComposition"
"AcmeTodo",
() => import("./sitemap/todo.ts")
);

// We can also register other fetchers for the `todoComposition`, for example:
// services.Sitemap.registerFetcher("todoComposition"
// "ContentfulTodo",
// () => import("./sitemap/contentful/todo.ts")
// );
},
},
});

Creating a Fetcher

🔗 documentation

The fetcher is runtime logic, which will be resolved when a request to the sitemap.xml page is made.

Here is an example of a fetcher that fetches a list of todos from a remote API, and generates the sitemap entry for each todo.

./extensions/acme/sitemap/todo.ts
import { createSitemapFetcher } from "@front-commerce/core";

export default createSitemapFetcher(async () => {
const posts = await fetch("https://jsonplaceholder.typicode.com/posts").then(
(res) => res.json()
);

return posts.map((post) => ({
route: `/todo/${post.id}`,
changefreq: "monthly",
lastmod: new Date(post.updatedAt),
priority: 0.5,
images: [post.image],
data: post, // we add the full post data which allows for custom filter logic
}));
});

Filtering sitemap entries from a fetcher

To filter specific entries from a fetcher, you can register a filter in your extension definition.

Filters are applied directly to the FetcherLeaf, so this allows different filters based on different FetcherLeaf's in the same composition.

./extensions/acme/index.ts
import { defineExtension } from "@front-commerce/core";

export default defineExtension({
name: "acme",
meta: import.meta,
unstable_lifecycleHooks: {
onServerServicesInit: async (services, request, config) => {
// We can add the data types through the generic <{ status: boolean }> typing
services.Sitemap.registerFetcherFilter<{ status: boolean }>(
"AcmeTodo",
// The entries returned are only those of the `AcmeTodo` fetcher leaf.
async (entries) => {
return entries.filter((entry) => entry.data?.status === "published");
}
);
},
},
});

Extending the type declarations

For TypeScript support, you'll need to extend the SitemapCompositionList and SitemapFetcherList interfaces from @front-commerce/types to include your custom composites and fetchers.

First ensure you have the @front-commerce/types package installed in your project, as it provides the necessary types for the SitemapService.

pnpm add -D @front-commerce/types

Then you can create a new types declaration file in your extension:

./extensions/acme/types/sitemap.d.ts
declare module "@front-commerce/types" {
export interface SitemapCompositionList {
todoComposition: "todoComposition";
}
export interface SitemapFetcherList {
AcmeTodo: "AcmeTodo";
}
}

These types will be merged with the existing types, so you can do this multiple times across multiple extensions.

important

The declaration file needs to be included in your tsconfig.json before typescript can pick it up.

tsconfig.json
{
"include": ["types/**/*"]
"include": ["extensions/**/types/**/*", "types/**/*"]
}

Opting Out of Sitemap Generation for static pages

Static pages are automatically included in the sitemap. To exclude a page from the sitemap, return null or an empty array from getSitemapEntries.

./extension/acme/routes/todo.tsx
import { createSeoHandle } from "@front-commerce/remix/seo";

export const handle = createSeoHandle({
getSitemapEntries: () => null,
});