T
TanStack8mo ago
noble-gold

Create API route at /sitemap.xml or /manifest.json

I'm trying to figure out how to create a handler that will response to /sitemap.xml or /manifest.json The difficulties I'm running in to are: - Wanting /api routes (no root, no layout, not html content-type) at a route other than /api - Wanting to include the . in the route itself - sitemap.xml.ts maps to /sitemap/xml in Tanstack file routing Has anyone figured this out?
11 Replies
genetic-orange
genetic-orange8mo ago
Just a hunch, I haven't tried it myself at all - what happens if you do (sitemap.xml).ts?
noble-gold
noble-goldOP8mo ago
That throws an error -
error: A route configuration for a route group was found at `api/(sitemap.xml).ts`. This is not supported. Did you mean to use a layout/pathless route instead?
error: A route configuration for a route group was found at `api/(sitemap.xml).ts`. This is not supported. Did you mean to use a layout/pathless route instead?
like-gold
like-gold8mo ago
I'd also want this to work. As a workaround, I'm adding custom routers to config (in app.config.ts), for example:
config.addRouter({
name: "sitemap",
type: "http",
base: "/sitemap.xml",
handler: "app/sitemap.ts",
} as RouterSchema);
config.addRouter({
name: "sitemap",
type: "http",
base: "/sitemap.xml",
handler: "app/sitemap.ts",
} as RouterSchema);
noble-gold
noble-goldOP8mo ago
Tbh, this is great - explicit exceptions to the norm are totally good with me. What does you app/sitemap.ts look like to actually serve a response? Am I just falling back to Vinxi at that point?
like-gold
like-gold8mo ago
This is stripped down version of sitemap.ts:
import { defineEventHandler } from "vinxi/http";

export default defineEventHandler(async () => {
const config = loadServerConfig();
const now = new Date();

const thisWeek = new Date(now);
thisWeek.setDate(now.getDate() - now.getDay());

const urls: SitemapUrl[] = [
"/",
].map((path) => ({
url: `${config.app.url}${path}`,
changeFrequency: "weekly",
lastModified: thisWeek,
}));

return new Response(makeSitemap(urls), {
status: 200,
headers: {
"Content-Type": "application/xml",
"X-Content-Type-Options": "nosniff",
"Cache-Control": "public, max-age=86400",
},
});
});
import { defineEventHandler } from "vinxi/http";

export default defineEventHandler(async () => {
const config = loadServerConfig();
const now = new Date();

const thisWeek = new Date(now);
thisWeek.setDate(now.getDate() - now.getDay());

const urls: SitemapUrl[] = [
"/",
].map((path) => ({
url: `${config.app.url}${path}`,
changeFrequency: "weekly",
lastModified: thisWeek,
}));

return new Response(makeSitemap(urls), {
status: 200,
headers: {
"Content-Type": "application/xml",
"X-Content-Type-Options": "nosniff",
"Cache-Control": "public, max-age=86400",
},
});
});
You're basically working with NitroJS handlers. I'm not sure which parts are part of Vinxi and which is just Nitro.
noble-gold
noble-goldOP8mo ago
For some reason when I curl localhost:3000/sitemap.xml, I get:
curl http://localhost:3000/sitemap.xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Redirecting</title>
</head>
<body>
<pre>Redirecting to /sitemap.xml/</pre>
</body>
</html>
curl http://localhost:3000/sitemap.xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Redirecting</title>
</head>
<body>
<pre>Redirecting to /sitemap.xml/</pre>
</body>
</html>
My app.config
import { defineConfig } from "@tanstack/start/config";
import type { RouterSchema } from "vinxi";
import tsConfigPaths from "vite-tsconfig-paths";

const config = defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ["./tsconfig.json"],
}),
],
},
});

config.addRouter({
name: "sitemap",
type: "http",
base: "/sitemap.xml",
handler: "app/routes/sitemap.ts",
} as RouterSchema);

export default config;
import { defineConfig } from "@tanstack/start/config";
import type { RouterSchema } from "vinxi";
import tsConfigPaths from "vite-tsconfig-paths";

const config = defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ["./tsconfig.json"],
}),
],
},
});

config.addRouter({
name: "sitemap",
type: "http",
base: "/sitemap.xml",
handler: "app/routes/sitemap.ts",
} as RouterSchema);

export default config;
My sitemap at app/routes/sitemap.ts
import { defineEventHandler } from "vinxi/http";

export default defineEventHandler(async () => {
return new Response("Hello World");
});
import { defineEventHandler } from "vinxi/http";

export default defineEventHandler(async () => {
return new Response("Hello World");
});
This happens if I move it out of app/routes and to app/sitemap.ts as well
like-gold
like-gold8mo ago
Hmm, I just noticed that. This did work for me with an older version. I fixed it by setting base: "/sitemap" w/o the xml extension, then fetching /sitemap.xml works, but so does any other subpath. I can check even.path === '.xml' in the event handler and throw 404 otherwise.
modern-teal
modern-teal8mo ago
cc @Sean Cassiere
noble-gold
noble-goldOP8mo ago
Hm, I still get the redirect, even with base: /sitemap
genetic-orange
genetic-orange8mo ago
This is flaw in how the base works. Currently, each Vinxi router is tied to a base (ie `/, /_build, /_server, /api - https://github.com/TanStack/router/blob/8f7c193ac68ce4ffaeae11b0a56c3cbd1a2baf29/packages/start/src/config/index.ts#L362). Because of this, at the moment anything hitting /* that doesn't match the others, gets send to the SSR router/handler which would try to render a React app. This'll likely see a big shake up with Tanner's plans for directly using Nitro, since we wouldn't be working with Vinxi's "router" concept, rather using whatever Nitro and Vite's Environment API provides.
GitHub
router/packages/start/src/config/index.ts at 8f7c193ac68ce4ffaeae11...
🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering. - TanStack/router
genetic-orange
genetic-orange8mo ago
For now, I'm not too sure what's the best way to add this sitemap route in. Possibly, the suggested way of adding in a Vinxi router. It's not perfect, but this workaround should be fine for now till Tanner's completed the big migrations that Start is currently going through.
// app.config.ts
import { defineConfig } from '@tanstack/start/config'
import tsConfigPaths from 'vite-tsconfig-paths'

const config = defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
],
},
})

config.addRouter({
name: 'sitemap',
type: 'http',
target: 'server',
base: '/sitemap',
handler: './sitemap.handler.ts',
plugins: () => [],
})

export default config
// app.config.ts
import { defineConfig } from '@tanstack/start/config'
import tsConfigPaths from 'vite-tsconfig-paths'

const config = defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
],
},
})

config.addRouter({
name: 'sitemap',
type: 'http',
target: 'server',
base: '/sitemap',
handler: './sitemap.handler.ts',
plugins: () => [],
})

export default config
// sitemap.handler.ts
import { defineEventHandler, toWebRequest } from 'vinxi/http'

export default defineEventHandler((event) => {
const request = toWebRequest(event)
const url = new URL(request.url)

if (url.pathname !== '/sitemap.xml') {
return new Response(null, { status: 302, headers: { Location: '/' } })
}

// sitemap in xml
return new Response(
`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2021-10-01</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>`,
{ headers: { 'Content-Type': 'application/xml' } },
)
})
// sitemap.handler.ts
import { defineEventHandler, toWebRequest } from 'vinxi/http'

export default defineEventHandler((event) => {
const request = toWebRequest(event)
const url = new URL(request.url)

if (url.pathname !== '/sitemap.xml') {
return new Response(null, { status: 302, headers: { Location: '/' } })
}

// sitemap in xml
return new Response(
`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2021-10-01</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
</urlset>`,
{ headers: { 'Content-Type': 'application/xml' } },
)
})

Did you find this page helpful?