diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index a1974107..8ea51c4d 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -26,15 +26,15 @@ export default defineConfig({ link: '/custom-routing', }, { - label: 'Self Declared Sitemap', - link: '/declarative-sitemap', - }, - { - label: 'Inline Module', + label: 'Sitemap Extensions', + link: '/sitemap-ext', badge: { text: 'NEW', variant: 'success', }, + }, + { + label: 'Inline Module', autogenerate: { directory: 'inline-mod' }, }, ], diff --git a/docs/src/content/docs/declarative-sitemap.md b/docs/src/content/docs/declarative-sitemap.md deleted file mode 100644 index 4b1bb039..00000000 --- a/docs/src/content/docs/declarative-sitemap.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Self Declared Sitemap ---- - -An extension of the [official `@astrojs/sitemap`](https://docs.astro.build/en/guides/integrations-guide/sitemap/) integration to allow each page to declare whether it should be included in the sitemap. - -```ts title="astro.config.mjs" del={1} add={2} -import sitemap from '@astrojs/sitemap'; -import sitemap from '@inox-tools/declarative-sitemap'; - -export default defineConfig({ - integration: [sitemap()], -}); -``` - -Export a boolean named `sitemap` on your pages and endpoints to define whether they should be included in the sitemap: - -```astro ---- -export const sitemap = true; ---- -``` - -## Configuration - -This extension accepts all the [options from `@astrojs/sitemap`](https://docs.astro.build/en/guides/integrations-guide/sitemap/#configuration), except for the `filter` function that is replaced by the declarative functionality. - -### `includeByDefault` - -**Type:** `boolean` -**Default:** `false` - -Whether pages should that do not explicitly declare `export const sitemap` should be included in the sitemap. diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 4aaee919..dee9326c 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -22,16 +22,21 @@ import InvisibleLink from '../../components/InvisibleLink.astro'; ## Next steps - - + + + An unofficial extension of the official `@astrojs/sitemap` integration. + + + + Define custom Astro Routes without relying on file-based routing. - - - - + + + + Pass inline JS values and functions as a virtual module to Vite projects. - - + + {/* Add Markdown or MDX files to `src/content/docs` to create new pages. diff --git a/docs/src/content/docs/sitemap-ext.md b/docs/src/content/docs/sitemap-ext.md new file mode 100644 index 00000000..f6886fc9 --- /dev/null +++ b/docs/src/content/docs/sitemap-ext.md @@ -0,0 +1,193 @@ +--- +title: Sitemap Extensions +description: An unofficial extension of the official @astrojs/sitemap integration. +packageName: '@inox-tools/sitemap-ext' +--- + +An unofficial extension of the official [`@astrojs/sitemap`](https://docs.astro.build/en/guides/integrations-guide/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`. + +```ts title="astro.config.mjs" del={1} add={2} +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`](https://docs.astro.build/en/guides/integrations-guide/sitemap/#configuration), except for the `filter` function that is replaced by the [per-route configuration](#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: + +```astro +--- +import sitemap from 'sitemap-ext:config'; + +sitemap(true); // opt-in + +sitemap(true); // opt-out +--- +``` + +In this case all pages created from the route will follow the given configuration. + +:::caution +This applies for all pre-rendered routes and to on-demand _static_ routes (routes that don't have dynamic parameters in their path). + +On-demand dynamic routes, like `/[...slug].astro`, can't use the full opt-in since the official sitemap integration that is used under the hood must generate the sitemap statically. +::: + +### 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: + +```ts +type ConfigHook = (callbacks: { + addToSitemap: (routeParams?: Record[]) => void; + removeFromSitemap: (routeParams?: Record[]) => void; + setSitemap: ( + routeParams: Array<{ sitemap?: boolean; params: Record }> + ) => void; +}) => Promise | void; +``` + +As noted by the type, the given hook function can be async, which means you can load remote information. + +```astro +--- +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](#includebydefault) + +#### `addToSitemap` / `removeFromSitemap` + +Calling these configuration callbacks without any argument has the same meaning as the [full opt in/out](#full-opt-inout) 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`](https://docs.astro.build/en/reference/api-reference/#params). + +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. + +```astro title="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](#includebydefault) + +Calling this function from a static route does nothing. + +```astro title="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. + +```astro title="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](#full-opt-inout) 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](https://medium.com/@vishalkamath853/self-healing-urls-eb66756a9c62) to support renaming your slugs over time. +- When your content can change per-request, but not the URLs.