GioJSdocs
On this page

sitemap.ts

Generate /sitemap.xml from code: app/sitemap.ts returns the list of URLs and GioJS writes the XML.

app/sitemap.ts
import type { MetadataRoute } from '@gio.js/core';
import { listPosts } from '../lib/posts';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await listPosts();
  return [
    { url: '/', changeFrequency: 'weekly', priority: 1 },
    ...posts.map((post) => ({ url: `/blog/${post.slug}`, lastModified: post.updatedAt })),
  ];
}

Reference

File name and location

app/sitemap.ts or app/sitemap.js, at the root of app/ only: a sitemap.ts in a subfolder is an ordinary module. It answers /sitemap.xml.

Exports

FieldTypeDefaultDescription
default (required)Sitemap | (() => Sitemap | Promise<Sitemap>)-The entries, or a function returning them. The function gets no arguments: the sitemap is the same for every visitor.
revalidatenumber | false3600Seconds the output is cached. false keeps it until the next deploy (one year), 0 (or a negative number) generates it on every request.

Sitemap entries

FieldTypeDefaultDescription
url (required)string-The page's URL, absolute or relative to GIO_SITE_URL. Becomes <loc>.
lastModifiedstring | Date-Becomes <lastmod>; a Date is written as an ISO timestamp, a string as given.
changeFrequency'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'-Becomes <changefreq>.
prioritynumber-From 0 to 1. Becomes <priority>.
alternates.languagesRecord<string, string>-Translations of the URL, by language: one <xhtml:link rel="alternate" hreflang> each.

The types are MetadataRoute.Sitemap (an array of SitemapEntry) and ChangeFrequency, from @gio.js/core.

Response

  • 200, Content-Type: application/xml; charset=utf-8, a urlset in the sitemaps.org namespace (with the xhtml namespace when an entry has alternates). Text is XML-escaped, and characters XML forbids are dropped.
  • Only GET and HEAD: other methods get 405 with Allow: GET, HEAD.
  • The server's page cache keeps the output for revalidate seconds (X-Gio-Cache shows hits) and answers with an ETag. Unlike HTML pages it gets no automatic Cache-Control; add a [[headers]] rule if a CDN should cache it.
  • An invalid entry (not an object, no url, an unknown changeFrequency, a priority outside 0-1, an invalid Date) or a function that throws answers 500 Internal Server Error (ref <digest>), never cached, with the reason in the log. A sitemap search engines would reject is never served.

Examples

Output

app/sitemap.ts
import type { MetadataRoute } from '@gio.js/core';

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    { url: '/', lastModified: new Date('2026-10-01T00:00:00Z'), changeFrequency: 'weekly', priority: 1 },
    { url: '/blog/hello', alternates: { languages: { de: '/de/blog/hello' } } },
  ];
}

With GIO_SITE_URL=https://example.com, /sitemap.xml is:

text
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/</loc>
<lastmod>2026-10-01T00:00:00.000Z</lastmod>
<changefreq>weekly</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://example.com/blog/hello</loc>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/blog/hello"/>
</url>
</urlset>

Refresh once a day

app/sitemap.ts
import type { MetadataRoute } from '@gio.js/core';
import { listProducts } from '../lib/catalog';

export const revalidate = 86400;

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const products = await listProducts();
  return products.map((product) => ({ url: `/products/${product.id}`, lastModified: product.updatedAt }));
}

Good to know

  • Relative URLs are resolved against GIO_SITE_URL (not metadataBase, which belongs to page metadata). Without it they stay relative and the server warns once per URL: crawlers need absolute URLs.
  • A public/sitemap.xml wins: it is served before the request reaches the worker, the module never runs, and startup warns. A page or route.ts at /sitemap.xml stops startup.
  • gio export writes out/sitemap.xml from it. Without an app/sitemap.ts, the export generates one listing every exported page, but only when GIO_SITE_URL is set.
  • One file serves one sitemap: there is no generateSitemaps for sitemap indexes yet. A sitemap holds at most 50,000 URLs, so split larger sites by hand with route.ts files.
  • In development a change to the file restarts the worker and clears the cache.

Version history

VersionChanges
v0.1.0-beta.8Introduced: app/sitemap.ts serves /sitemap.xml, on the server, in standalone builds and in gio export.
v0.1.0-beta.3gio export generates a sitemap.xml of the exported pages when GIO_SITE_URL is set.