Skip to content

Commit ef89ba2

Browse files
authored
Merge pull request #5 from solidjs/announce-either-router
solid: announceRoutes takes either router
2 parents a11368a + f272770 commit ef89ba2

5 files changed

Lines changed: 60 additions & 16 deletions

File tree

.changeset/http-transport-cli-redirects.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Prerender anything over HTTP, from the command line, with redirects the host can
1919

2020
**Query-string pages.** `keepQuery: true` renders `/posts?page=2` apart from `/posts` (parameters sorted for dedupe), following its links and capturing its data, but writes it only when its seed entry names a `filename` — a static host serves a path the same for every query. Off by default; CLI `--keep-query`.
2121

22-
**Router seeding, from the server.** New `prerender-crawler/routers` (no Node imports — for application server code): `tanstackRouterPages(router)` and `solidRouterPages(router | routes, { base? })` list a router's static pages from the instance the app built for the request (leaves and indexes, no params or splats), and `announcePages(request, headers, paths)` puts them on the response's hint header when the request is the crawler's. `@solidjs/prerender` gains `announceRoutes(Router)` — one line in the app root, reading the ambient request event; a no-op in the browser. The Vite plugin, the CLI against a module, and the CLI against a running server all seed from the same header.
22+
**Router seeding, from the server.** New `prerender-crawler/routers` (no Node imports — for application server code): `tanstackRouterPages(router)` and `solidRouterPages(router | routes, { base? })` list a router's static pages from the instance the app built for the request (leaves and indexes, no params or splats), and `announcePages(request, headers, paths)` puts them on the response's hint header when the request is the crawler's. `@solidjs/prerender` gains `announceRoutes(router)` — one line in the app root (Solid Router) or the request setup (TanStack Router); it takes either router by shape, reads the ambient request event, and is a no-op in the browser. The Vite plugin, the CLI against a module, and the CLI against a running server all seed from the same header.
2323

2424
**Removed:** `fileRoutePages`, `staticRoutePaths`, and the Vite plugin's `fileRoutes` option — build-time seeding that walked a `filesystem-routing` directory and re-derived its path rules. The server's own router is the source of truth for which pages exist, and the header works for crawls that never see the project's disk (the CLI against a running server). `filesystem-routing` is no longer a peer dependency.
2525

packages/solid/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ export default function App() {
6161

6262
On the server, when the request is the crawler's, the router's static pages go on the response's hint header and the crawl seeds every one of them. A visitor's response is untouched; in the browser it is a no-op. Dynamic routes (`/posts/:id`) are not announced — only a render knows their values; the crawl finds them by their links. Options: `header` (a custom crawl `hintHeader`), `base`.
6363

64+
It takes either router Solid apps use. With TanStack Router the instance is built per request, so the call goes where the router is — the `start.setup` hook:
65+
66+
```ts
67+
// src/setup.tsx
68+
const router = createAppRouter(queryClient, history);
69+
announceRoutes(router);
70+
```
71+
6472
## `serverFunctions(options?)`
6573

6674
The integration has two jobs.

packages/solid/src/client.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ import type { AnnounceRoutesOptions, PrerenderedFunction } from "./shared.ts";
2626
export { staticArtifactPath, staticCallKey } from "./shared.ts";
2727
export type { AnnounceRoutesOptions, PrerenderedFunction } from "./shared.ts";
2828

29+
/** A router `announceRoutes` can read — see the server half. */
30+
export type AnnounceableRouter = unknown;
31+
2932
/** The client half of `announceRoutes`: there is no request here. Always false. */
30-
export function announceRoutes(_router: unknown, _options?: AnnounceRoutesOptions): boolean {
33+
export function announceRoutes(
34+
_router: AnnounceableRouter,
35+
_options?: AnnounceRoutesOptions
36+
): boolean {
3137
return false;
3238
}
3339

packages/solid/src/server.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,34 @@ import {
1414
} from "@solidjs/web/server-functions/server";
1515
import { getRequestEvent } from "@solidjs/web";
1616
import type { RequestEvent, ResponseStub } from "@solidjs/web";
17-
import { announcePages, solidRouterPages } from "prerender-crawler/routers";
18-
import type { SolidRouteLike, SolidRouterLike } from "prerender-crawler/routers";
17+
import { announcePages, solidRouterPages, tanstackRouterPages } from "prerender-crawler/routers";
18+
import type {
19+
SolidRouteLike,
20+
SolidRouterLike,
21+
TanStackRouterLike
22+
} from "prerender-crawler/routers";
1923
import { CAPTURE_SINK, PRERENDERED_META_KEY } from "./shared.ts";
2024
import type { AnnounceRoutesOptions, CaptureSink, PrerenderedFunction } from "./shared.ts";
2125

2226
export { staticArtifactPath, staticCallKey } from "./shared.ts";
2327
export type { CaptureSink, PrerenderedFunction } from "./shared.ts";
2428
export type { AnnounceRoutesOptions } from "./shared.ts";
2529

30+
/** A router `announceRoutes` can read: Solid Router (instance or tree) or a TanStack Router instance. */
31+
export type AnnounceableRouter =
32+
SolidRouterLike | SolidRouteLike | readonly SolidRouteLike[] | TanStackRouterLike;
33+
2634
/**
2735
* Tells a prerender crawl which pages this app's router has — the server
28-
* half. Called during a server render (the app root is the natural place),
29-
* it reads the ambient request: when the request is the crawler's, the
30-
* router's static pages go on the response's hint header and the crawl
31-
* seeds every one of them, linked or not. A visitor's request is untouched;
32-
* on the client this is a no-op. Returns whether it announced.
36+
* half. Called during a server render or request setup, it reads the
37+
* ambient request: when the request is the crawler's, the router's static
38+
* pages go on the response's hint header and the crawl seeds every one of
39+
* them, linked or not. A visitor's request is untouched; on the client
40+
* this is a no-op. Returns whether it announced.
41+
*
42+
* Takes either router Solid apps use — a Solid Router `createRouter`
43+
* instance (or its route-definition tree, with `base`) or a TanStack
44+
* Router instance — and tells them apart by shape.
3345
*
3446
* ```tsx
3547
* import { announceRoutes } from "@solidjs/prerender";
@@ -41,20 +53,23 @@ export type { AnnounceRoutesOptions } from "./shared.ts";
4153
* }
4254
* ```
4355
*
44-
* Dynamic routes (`/posts/:id`) are not announced — only a render knows
45-
* their values; the crawl finds them by their links.
56+
* Dynamic routes (`/posts/:id`, `/posts/$id`) are not announced — only a
57+
* render knows their values; the crawl finds them by their links.
4658
*/
47-
export function announceRoutes(
48-
router: SolidRouterLike | SolidRouteLike | readonly SolidRouteLike[],
49-
options: AnnounceRoutesOptions = {}
50-
): boolean {
59+
export function announceRoutes(router: AnnounceableRouter, options: AnnounceRoutesOptions = {}) {
5160
const event = getRequestEvent() as (RequestEvent & { response?: ResponseStub }) | undefined;
5261
if (!event?.response || event.response.committed) return false;
5362
if (!event.request.headers.has(options.header ?? "x-prerender")) return false;
54-
const pages = solidRouterPages(router, { base: options.base });
63+
const pages = isTanStackRouter(router)
64+
? tanstackRouterPages(router)
65+
: solidRouterPages(router, { base: options.base });
5566
return announcePages(event.request, event.response.headers, pages, { header: options.header });
5667
}
5768

69+
// a TanStack instance carries its path index; nothing of Solid Router's does
70+
const isTanStackRouter = (router: AnnounceableRouter): router is TanStackRouterLike =>
71+
typeof router === "object" && router !== null && "routesByPath" in router;
72+
5873
const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
5974

6075
/**

packages/solid/test/announce.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ describe("announceRoutes", () => {
5050
});
5151
});
5252

53+
it("reads a TanStack Router instance by shape", () => {
54+
const tanstack = {
55+
routesByPath: {
56+
"/": { fullPath: "/" },
57+
"/users": { fullPath: "/users/", children: [{}] },
58+
"/users/$id": { fullPath: "/users/$id" }
59+
}
60+
};
61+
const request = new Request("http://localhost/", { headers: { "x-prerender": "1" } });
62+
withEvent(request, event => {
63+
expect(announceRoutes(tanstack)).toBe(true);
64+
expect(event.response.headers.get("x-prerender")!.split(",").sort()).toEqual(["/", "/users"]);
65+
});
66+
});
67+
5368
it("is a no-op on the client", () => {
5469
expect(announceClient(Router)).toBe(false);
5570
});

0 commit comments

Comments
 (0)