Add sitemap generation and bunext sitemap command

- Add generateSitemap util (exported as bunext.bunextGenerateSitemap)
  that builds sitemap.xml from static pages, with exclude patterns,
  additional paths (array or sync/async function), lastmod from file
  mtime, trailing slash, and default changefreq/priority options
- Let page .server.ts files export a `sitemap` function to list URLs
  for dynamic routes, or override a static page's entry
- Add `bunext sitemap` command reading `sitemap` from bunext.config.ts,
  writing public/sitemap.xml by default, with --output/--origin flags
- Fix handle-routes test leaking a grab-router module mock into
  bunext-req-handler tests; set global.BUNEXT_ROUTER instead
- Ignore /.tmp
- Document sitemap usage in README and CLAUDE.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-14 17:41:21 +01:00
co-authored by Claude Opus 5.5
parent 3025b1681d
commit d9de858439
11 changed files with 917 additions and 11 deletions
+85
View File
@@ -110,6 +110,10 @@ export type BunextConfig = {
* Public environment variables to be passed to the client
*/
public_envs?: Record<string, string>;
/**
* Options for the `bunext sitemap` command
*/
sitemap?: BunextSitemapConfig;
};
export type BunextConfigMiddlewareParams = {
@@ -216,8 +220,21 @@ export type BunextPageModule = {
export type BunextPageServerModule = {
default?: BunextPageServerFn;
server?: BunextPageServerFn;
sitemap?: BunextPageSitemapFn;
};
/**
* Returns the sitemap entries for a page. Required for dynamic routes
* (`[slug]`) to appear in the sitemap. On a static page, the returned
* entries replace the page's default entry.
*/
export type BunextPageSitemapFn = (params: {
/**
* The page's route pattern, e.g. `/blog/[slug]`
*/
route: string;
}) => BunextSitemapAdditionalPaths | Promise<BunextSitemapAdditionalPaths>;
export type BunextHTMLProps = DetailedHTMLProps<
HtmlHTMLAttributes<HTMLHtmlElement>,
HTMLHtmlElement
@@ -452,3 +469,71 @@ export type BuildDataType = {
build_date_unix: number;
build_date: string;
};
export type BunextSitemapChangeFreq =
| "always"
| "hourly"
| "daily"
| "weekly"
| "monthly"
| "yearly"
| "never";
export type BunextSitemapEntry = {
/**
* URL path (e.g. `/blog/my-post`) or absolute URL.
*/
path: string;
lastmod?: Date | string;
changefreq?: BunextSitemapChangeFreq;
/**
* Between `0.0` and `1.0`.
*/
priority?: number;
};
export type BunextSitemapAdditionalPaths = (string | BunextSitemapEntry)[];
export type BunextGenerateSitemapParams = {
/**
* Base URL prepended to every path. Defaults to `config.origin`,
* falling back to `http://localhost:<port>`.
*/
origin?: string;
/**
* Paths to leave out. A string matches a path exactly, or a whole
* subtree when it ends with `/*` (e.g. `/admin/*`). A RegExp is
* tested against the path.
*/
exclude?: (string | RegExp)[];
/**
* Extra entries, typically concrete URLs for dynamic routes
* (`[slug]`), which cannot be discovered from the file system. Can
* also be a function (sync or async) returning them, e.g. to load
* them from a database.
*/
additional_paths?:
| BunextSitemapAdditionalPaths
| (() =>
| BunextSitemapAdditionalPaths
| Promise<BunextSitemapAdditionalPaths>);
/**
* Use each page file's modification time as `lastmod`. Defaults to
* `true`.
*/
use_file_lastmod?: boolean;
/**
* Append a trailing slash to every non-root path. Defaults to `false`.
*/
trailing_slash?: boolean;
default_changefreq?: BunextSitemapChangeFreq;
default_priority?: number;
};
export type BunextSitemapConfig = BunextGenerateSitemapParams & {
/**
* Output file, relative to the project root. Defaults to
* `public/sitemap.xml`, which is served at `/sitemap.xml`.
*/
output?: string;
};