Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Document sitemap extension #41

Merged
merged 8 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
],
Expand Down
33 changes: 0 additions & 33 deletions docs/src/content/docs/declarative-sitemap.md

This file was deleted.

21 changes: 13 additions & 8 deletions docs/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,21 @@ import InvisibleLink from '../../components/InvisibleLink.astro';
## Next steps

<CardGrid stagger>
<InvisibleLink href="/custom-routing">
<Card title="Custom Astro Routing" icon="add-document">
<Card title="Sitemap Extensions" icon="document">
<InvisibleLink href="/sitemap-ext">
An unofficial extension of the official `@astrojs/sitemap` integration.
</InvisibleLink>
</Card>
<Card title="Custom Astro Routing" icon="add-document">
<InvisibleLink href="/custom-routing">
Define custom Astro Routes without relying on file-based routing.
</Card>
</InvisibleLink>
<InvisibleLink href="/inline-mod">
<Card title="Inline Virtual Module" icon="pencil">
</InvisibleLink>
</Card>
<Card title="Inline Virtual Module" icon="pencil">
<InvisibleLink href="/inline-mod">
Pass inline JS values and functions as a virtual module to Vite projects.
</Card>
</InvisibleLink>
</InvisibleLink>
</Card>
{/* <Card title="Add new content" icon="add-document">
Add Markdown or MDX files to `src/content/docs` to create new pages.
</Card>
Expand Down
193 changes: 193 additions & 0 deletions docs/src/content/docs/sitemap-ext.md
Original file line number Diff line number Diff line change
@@ -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<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.

```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.
Fryuni marked this conversation as resolved.
Show resolved Hide resolved

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.
Loading