Skip to content

Commit bbfc9e4

Browse files
committed
feat(router): documentHead via serverData
the createRenderer type was already updated in the previous commit
1 parent 7d75134 commit bbfc9e4

File tree

10 files changed

+82
-17
lines changed

10 files changed

+82
-17
lines changed

.changeset/stale-corners-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/router': minor
3+
---
4+
5+
feat: You can now put `documentHead` into the rendering functions as part of the `serverData` option. This is useful for passing title, meta tags, scripts, etc. to the `useDocumentHead()` hook from within the server.

packages/docs/src/routes/docs/(qwikrouter)/pages/index.mdx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,31 @@ export const head: DocumentHead = ({resolveValue, params}) => {
121121
};
122122
```
123123

124+
### Server-injected Head
125+
126+
You can also pass `documentHead` to `createRenderer()` as part of the `serverData` option.
127+
128+
The values passed will be used as the default values for `useDocumentHead()`, before the `head` exports are resolved.
129+
130+
```tsx title="src/entry.ssr.tsx" {10}
131+
import { createRenderer } from "@qwik.dev/router";
132+
import Root from "./root";
133+
134+
export default createRenderer((opts) => {
135+
return {
136+
app: <Root />,
137+
options: {
138+
...opts,
139+
serverData: {
140+
...opts.serverData,
141+
documentHead: {
142+
title: "My App",
143+
},
144+
},
145+
}
146+
});
147+
```
148+
124149
### Nested Layouts and Head
125150
126151
In an advanced case, a [layout](/docs/(qwikrouter)/layout/index.mdx) may want to modify the document title of an already resolved document head. In the example below, the page component returns the title of `Foo`. The containing layout component can read the value of the page's document head and modify it. In this example, the layout component is adding `MyCompany - ` to the title, so that when rendered, the title will be `MyCompany - Foo`. Every layout in the stack has the opportunity to return a new value.

packages/qwik-router/src/runtime/src/head.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ export const resolveHead = (
1818
endpoint: EndpointResponse | ClientPageData,
1919
routeLocation: RouteLocation,
2020
contentModules: ContentModule[],
21-
locale: string
21+
locale: string,
22+
defaults?: DocumentHeadValue
2223
) => {
23-
const head = createDocumentHead();
24+
const head = createDocumentHead(defaults);
2425
const getData = ((loaderOrAction: LoaderInternal | ActionInternal) => {
2526
const id = loaderOrAction.__id;
2627
if (loaderOrAction.__brand === 'server_loader') {
@@ -92,11 +93,11 @@ const mergeArray = (
9293
}
9394
};
9495

95-
export const createDocumentHead = (): ResolvedDocumentHead => ({
96-
title: '',
97-
meta: [],
98-
links: [],
99-
styles: [],
100-
scripts: [],
101-
frontmatter: {},
96+
export const createDocumentHead = (defaults?: DocumentHeadValue): ResolvedDocumentHead => ({
97+
title: defaults?.title || '',
98+
meta: [...(defaults?.meta || [])],
99+
links: [...(defaults?.links || [])],
100+
styles: [...(defaults?.styles || [])],
101+
scripts: [...(defaults?.scripts || [])],
102+
frontmatter: { ...defaults?.frontmatter },
102103
});

packages/qwik-router/src/runtime/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export type {
4040
RouteData,
4141
RouteLocation,
4242
RouteNavigate,
43+
ServerData,
4344
StaticGenerate,
4445
StaticGenerateHandler,
4546
ValidatorErrorKeyDotNotation,

packages/qwik-router/src/runtime/src/qwik-router-component.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import type {
5555
ContentModule,
5656
ContentState,
5757
ContentStateInternal,
58+
DocumentHeadValue,
5859
Editable,
5960
EndpointResponse,
6061
LoadedRoute,
@@ -150,6 +151,7 @@ export const QwikRouterProvider = component$<QwikRouterProps>((props) => {
150151
if (!urlEnv) {
151152
throw new Error(`Missing Qwik URL Env Data`);
152153
}
154+
const serverHead = useServerData<DocumentHeadValue>('documentHead');
153155

154156
if (isServer) {
155157
if (
@@ -217,7 +219,9 @@ export const QwikRouterProvider = component$<QwikRouterProps>((props) => {
217219
replaceState: false,
218220
scroll: true,
219221
});
220-
const documentHead = useStore<Editable<ResolvedDocumentHead>>(createDocumentHead);
222+
const documentHead = useStore<Editable<ResolvedDocumentHead>>(() =>
223+
createDocumentHead(serverHead)
224+
);
221225
const content = useStore<Editable<ContentState>>({
222226
headings: undefined,
223227
menu: undefined,
@@ -487,7 +491,13 @@ export const QwikRouterProvider = component$<QwikRouterProps>((props) => {
487491
(routeInternal as any).untrackedValue = { type: navType, dest: trackUrl };
488492

489493
// Needs to be done after routeLocation is updated
490-
const resolvedHead = resolveHead(clientPageData!, routeLocation, contentModules, locale);
494+
const resolvedHead = resolveHead(
495+
clientPageData!,
496+
routeLocation,
497+
contentModules,
498+
locale,
499+
serverHead
500+
);
491501

492502
// Update content
493503
content.headings = pageModule.headings;

starters/apps/base/src/entry.ssr.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ export default createRenderer((opts) => {
1919
lang: "en-us",
2020
...opts.containerAttributes,
2121
},
22+
serverData: {
23+
...opts.serverData,
24+
// These are the default values for the document head and are overridden by the `head` exports
25+
// documentHead: {
26+
// title: "My App",
27+
// },
28+
},
2229
},
2330
};
2431
});

starters/apps/qwikrouter-test/src/entry.ssr.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,14 @@ export default function (opts: RenderToStreamOptions) {
88
return renderToStream(<Root />, {
99
base: "/qwikrouter-test/build/",
1010
...opts,
11+
serverData: {
12+
...opts.serverData,
13+
// ensure that documentHead injection works
14+
documentHead: {
15+
title: "Qwik Router Test",
16+
meta: [{ name: "hello", content: "world" }],
17+
scripts: [{ key: "hello", script: 'window.hello = "world";' }],
18+
},
19+
},
1120
});
1221
}

starters/apps/qwikrouter-test/src/routes/(common)/index.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { component$ } from "@qwik.dev/core";
2-
import type { DocumentHead } from "@qwik.dev/router";
32
// @ts-ignore
43
import ImageJpeg from "../../media/MyTest.jpeg?jsx";
54
// @ts-ignore
@@ -18,7 +17,3 @@ export default component$(() => {
1817
</div>
1918
);
2019
});
21-
22-
export const head: DocumentHead = {
23-
title: "Welcome to Qwik Router",
24-
};

starters/e2e/qwikrouter/head.e2e.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test.describe("Qwik Router documentHead", () => {
4+
test("pass documentHead to Qwik", async ({ page }) => {
5+
await page.goto("/qwikrouter-test/");
6+
// injected title via renderToStream serverData
7+
await expect(page).toHaveTitle("Qwik Router Test - Qwik");
8+
const meta = page.locator("meta[name='hello']");
9+
await expect(meta).toHaveAttribute("content", "world");
10+
expect(await page.evaluate(() => (window as any).hello)).toBe("world");
11+
});
12+
});

starters/e2e/qwikrouter/page.e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function tests() {
2727
/*********** Home Page ***********/
2828
await assertPage(ctx, {
2929
pathname: "/qwikrouter-test/",
30-
title: "Welcome to Qwik Router - Qwik",
30+
title: "Qwik Router Test - Qwik",
3131
layoutHierarchy: ["root"],
3232
h1: "Welcome to Qwik Router",
3333
activeHeaderLink: false,

0 commit comments

Comments
 (0)