+
Skip to content

feat(vite-plugin-nitro): allow customization of sitemap definition for prerendered routes #1650

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

Merged
merged 1 commit into from
Mar 18, 2025
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
16 changes: 15 additions & 1 deletion apps/blog-app/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import analog, { type PrerenderContentFile } from '@analogjs/platform';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { defineConfig } from 'vite';
import fs from 'node:fs';

// Only run in Netlify CI
let base = process.env['URL'] || 'http://localhost:3000';
Expand Down Expand Up @@ -52,7 +53,14 @@ export default defineConfig(() => {
'/blog',
'/about',
'/api/rss.xml',
'/blog/2022-12-27-my-first-post',
{
route: '/blog/2022-12-27-my-first-post',
sitemap: () => {
return {
lastmod: '2022-12-27',
};
},
},
'/blog/my-second-post',
'/about-me',
'/about-you',
Expand All @@ -64,6 +72,12 @@ export default defineConfig(() => {
}
return `/archived/${file.attributes.slug || file.name}`;
},
sitemap: (file: PrerenderContentFile) => {
console.log(file.name);
return {
changefreq: 'never',
};
},
},
];
},
Expand Down
61 changes: 55 additions & 6 deletions apps/docs-app/docs/features/server/static-site-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export default defineConfig(({ mode }) => ({

To only prerender the static pages, use the `static: true` flag.

> The `ssr` flag must still be set to `true` for prerendering static pages.

```ts
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';
Expand Down Expand Up @@ -109,8 +111,7 @@ The static pages can be deployed from the `dist/analog/public` directory.

### Sitemap Generation

Analog also supports automatic sitemap generation. Analog generates a sitemap in the `dist/analog/public`
directory when running a build if a sitemap configuration is provided.
Analog also supports automatic sitemap generation. Analog generates a sitemap in the `dist/analog/public` directory when running a build if a sitemap configuration is provided.

```ts
import { defineConfig } from 'vite';
Expand All @@ -131,8 +132,51 @@ export default defineConfig(({ mode }) => ({
}));
```

As long as routes are provided, Analog generates a `sitemap.xml` file containing a
mapping of the pages' `<loc>` and `<lastmod>` properties.
To customize the sitemap definition, use the `sitemap` callback function to customize the `lastmod`, `changefreq`, and `priority` fields.

```ts
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';
import fs from 'node:fs';

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
plugins: [
analog({
prerender: {
sitemap: {
host: 'https://analogjs.org/',
},
routes: async () => [
'/',
'/blog',
{
route: '/blog/2022-12-27-my-first-post',
sitemap: {
lastmod: '2022-12-27',
},
},
{
contentDir: '/src/content/archived',
transform: (file: PrerenderContentFile) => {
return `/archived/${file.attributes.slug || file.name}`;
},
sitemap: (file: PrerenderContentFile) => {
return {
lastmod: 'read last modified date for content file',
changefreq: 'never',
};
},
},
],
},
}),
],
}));
```

As long as prerender routes are provided, Analog generates a `sitemap.xml` file containing a
mapping of the pages' `<loc>`, `<lastmod>`, `<changefreq>`, and `<priority>` properties.

```xml
<?xml version="1.0" encoding="UTF-8"?>
Expand All @@ -143,8 +187,13 @@ mapping of the pages' `<loc>` and `<lastmod>` properties.
<lastmod>2023-07-01</lastmod>
</url>
<url>
<loc>https://analogjs.org/blog</loc>
<lastmod>2023-07-01</lastmod>
<loc>https://analogjs.org/blog/2022-12-27-my-first-post</loc>
<lastmod>2022-12-27</lastmod>
</url>
<url>
<loc>https://analogjs.org/blog/archived/hello-world</loc>
<lastmod>2022-12-01</lastmod>
<changefreq>never</changefreq>
</url>
</urlset...>
```
Expand Down
42 changes: 11 additions & 31 deletions packages/platform/src/lib/options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { PluginOptions } from '@analogjs/vite-plugin-angular';
import type { NitroConfig, PrerenderRoute } from 'nitropack';
import type { SitemapConfig } from '@analogjs/vite-plugin-nitro';
import type {
SitemapConfig,
PrerenderContentDir,
PrerenderContentFile,
PrerenderRouteConfig,
} from '@analogjs/vite-plugin-nitro';

import { ContentPluginOptions } from './content-plugin.js';

Expand All @@ -24,8 +29,10 @@ export interface PrerenderOptions {
* List of routes to prerender resolved statically or dynamically.
*/
routes?:
| (string | PrerenderContentDir)[]
| (() => Promise<(string | PrerenderContentDir | undefined)[]>);
| (string | PrerenderContentDir | PrerenderRouteConfig)[]
| (() => Promise<
(string | PrerenderContentDir | PrerenderRouteConfig | undefined)[]
>);
sitemap?: SitemapConfig;
/** List of functions that run for each route after pre-rendering is complete. */
postRenderingHooks?: ((routes: PrerenderRoute) => Promise<void>)[];
Expand Down Expand Up @@ -81,31 +88,4 @@ export interface Options {
disableTypeChecking?: boolean;
}

export interface PrerenderContentDir {
/**
* The directory or glob pattern where the files should be grabbed from.
* @example `/src/contents/blog`
*/
contentDir: string;
/**
* Transform the matching content files path into a route.
* The function is called for each matching content file within the specified contentDir.
* @param file information of the matching file (`path`, `name`, `extension`, `attributes`)
* @returns a string with the route should be returned (e. g. `/blog/<slug>`) or the value `false`, when the route should not be prerendered.
*/
transform: (file: PrerenderContentFile) => string | false;
}

/**
* @param path the path to the content file
* @param name the basename of the matching content file without the file extension
* @param extension the file extension
* @param attributes the frontmatter attributes extracted from the frontmatter section of the file
* @returns a string with the route should be returned (e. g. `/blog/<slug>`) or the value `false`, when the route should not be prerendered.
*/
export interface PrerenderContentFile {
path: string;
attributes: Record<string, any>;
name: string;
extension: string;
}
export { PrerenderContentDir, PrerenderContentFile };
8 changes: 7 additions & 1 deletion packages/vite-plugin-nitro/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { nitro } from './lib/vite-plugin-nitro.js';
export { Options, SitemapConfig } from './lib/options.js';
export {
Options,
SitemapConfig,
PrerenderRouteConfig,
PrerenderContentDir,
PrerenderContentFile,
} from './lib/options.js';

declare module 'nitropack' {
interface NitroRouteConfig {
Expand Down
36 changes: 31 additions & 5 deletions packages/vite-plugin-nitro/src/lib/build-sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,60 @@ import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
import { create } from 'xmlbuilder2';
import { UserConfig } from 'vite';
import { resolve } from 'node:path';
import { SitemapConfig } from './options';
import {
PrerenderContentFile,
PrerenderSitemapConfig,
SitemapConfig,
} from './options';

export type PagesJson = {
page: string;
lastMod: string;
changefreq?: string;
priority?: string;
};

export async function buildSitemap(
config: UserConfig,
sitemapConfig: SitemapConfig,
routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),
outputDir: string,
routeSitemaps: Record<
string,
PrerenderSitemapConfig | (() => PrerenderSitemapConfig) | undefined
>,
) {
const routeList: string[] = await optionHasRoutes(routes);

if (routeList.length) {
const slash = checkSlash(sitemapConfig.host || '');
const sitemapData: PagesJson[] = routeList.map((page: string) => ({
page: `${sitemapConfig.host}${slash}${page.replace(/^\/+/g, '')}`,
lastMod: new Date().toISOString().split('T')[0],
}));
const sitemapData: PagesJson[] = routeList.map((page: string) => {
const url = `${slash}${page.replace(/^\/+/g, '')}`;
const config = routeSitemaps[url];
const props = typeof config === 'object' ? config : config?.();

return {
page: `${sitemapConfig.host}${url}`,
lastMod: props?.lastmod ?? new Date().toISOString().split('T')[0],
changefreq: props?.changefreq,
priority: props?.priority,
};
});

const sitemap = createXml('urlset');

for (const item of sitemapData) {
const page = sitemap.ele('url');
page.ele('loc').txt(item.page);
page.ele('lastmod').txt(item.lastMod);

if (item.changefreq) {
page.ele('changefreq').txt(item.changefreq);
}

if (item.priority) {
page.ele('priority').txt(item.priority);
}
}

const mapPath = `${resolve(outputDir)}/sitemap.xml`;
Expand Down
38 changes: 36 additions & 2 deletions packages/vite-plugin-nitro/src/lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ export interface PrerenderOptions {
* List of routes to prerender resolved statically or dynamically.
*/
routes?:
| (string | PrerenderContentDir)[]
| (() => Promise<(string | PrerenderContentDir | undefined)[]>);
| (string | PrerenderContentDir | PrerenderRouteConfig)[]
| (() => Promise<
(string | PrerenderContentDir | PrerenderRouteConfig | undefined)[]
>);
sitemap?: SitemapConfig;
/** List of functions that run for each route after pre-rendering is complete. */
postRenderingHooks?: ((routes: PrerenderRoute) => Promise<void>)[];
Expand All @@ -67,6 +69,15 @@ export interface PrerenderContentDir {
* @returns a string with the route should be returned (e. g. `/blog/<slug>`) or the value `false`, when the route should not be prerendered.
*/
transform: (file: PrerenderContentFile) => string | false;

/**
* Customize the sitemap definition for the prerendered route
*
* https://www.sitemaps.org/protocol.html#xmlTagDefinitions
*/
sitemap?:
| PrerenderSitemapConfig
| ((file: PrerenderContentFile) => PrerenderSitemapConfig);
}

/**
Expand All @@ -82,3 +93,26 @@ export interface PrerenderContentFile {
name: string;
extension: string;
}

export interface PrerenderSitemapConfig {
lastmod?: string;
changefreq?:
| 'always'
| 'hourly'
| 'daily'
| 'weekly'
| 'monthly'
| 'yearly'
| 'never';
priority?: string;
}

export interface PrerenderRouteConfig {
route: string;
/**
* Customize the sitemap definition for the prerendered route
*
* https://www.sitemaps.org/protocol.html#xmlTagDefinitions
*/
sitemap?: PrerenderSitemapConfig | (() => PrerenderSitemapConfig);
}
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载