Skip to content
Open
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
21 changes: 14 additions & 7 deletions packages/preact/src/entry-server.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { useContext } from "preact/hooks";
import renderToString from "preact-render-to-string";
import type { ComponentType } from "preact";
import type { Context, RouteModule } from "@impalajs/core";
import { HeadContext } from "./head-context";
import { HeadContext, HeadManager } from "./head-context";

function HeadContent() {
const headProvider = useContext(HeadContext);
return <>{...headProvider.getHead()}</>;
function HeadContent({ headManager }: { headManager: HeadManager }) {
return <>{...headManager.getHead()}</>;
}

export async function render(
Expand All @@ -16,13 +14,22 @@ export async function render(
) {
const { default: Page } = await mod();

const body = renderToString(<Page {...context} />);
// We create a new head manager for each request to avoid sharing state across routes
const headManager = new HeadManager();

// Now on each render, each page will use their own head context instead of default one
const body = renderToString(
<HeadContext.Provider value={headManager}>
<Page {...context} />
</HeadContext.Provider>
);

const modules = bootstrapModules?.map(
(m) => `<script type="module" src="${m}"></script>`
);

const headContent = renderToString(<HeadContent />);
// We then pass the head manager instance that is specific to this request to SSR
const headContent = renderToString(<HeadContent headManager={headManager} />);

return {
body,
Expand Down
6 changes: 3 additions & 3 deletions packages/preact/src/head-context.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createContext, VNode } from "preact";

class HeadProvider {
export class HeadManager {
private head: VNode<any>[] = [];

private removeTag(tag: string) {
Expand Down Expand Up @@ -31,6 +31,6 @@ class HeadProvider {
}
}

const headProvider = new HeadProvider();
const defaultHeadProvider = new HeadManager();

export const HeadContext = createContext(headProvider);
export const HeadContext = createContext(defaultHeadProvider);
41 changes: 25 additions & 16 deletions packages/react/src/entry-server.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
import { ElementType, useContext } from "react";
import { ElementType } from "react";
import type { Context, RouteModule } from "@impalajs/core";
import { Writable, WritableOptions } from "node:stream";
import { HeadContext } from "./head-context";
import { HeadContext, HeadManager } from "./head-context";

class StringResponse extends Writable {
private buffer: string;
Expand Down Expand Up @@ -30,9 +30,8 @@ class StringResponse extends Writable {
}
}

function HeadContent() {
const headProvider = useContext(HeadContext);
return <>{...headProvider.getHead()}</>;
function HeadContent({ headManager }: { headManager: HeadManager }) {
return <>{...headManager.getHead()}</>;
}

export async function render(
Expand All @@ -42,22 +41,32 @@ export async function render(
) {
const { default: Page } = await mod();

// We create a new head manager for each request to avoid sharing state across routes
const headManager = new HeadManager();

const response = new StringResponse();

const { pipe } = renderToPipeableStream(<Page {...context} />, {
bootstrapModules,
bootstrapScriptContent: `window.___CONTEXT=${JSON.stringify(context)};`,
onAllReady() {
pipe(response);
},
onError(error) {
console.error(error);
},
});
const { pipe } = renderToPipeableStream(
// Now on each render, each page will use their own head context instead of default one
(
<HeadContext.Provider value={headManager}>
<Page {...context} />
</HeadContext.Provider>
),
{
bootstrapModules,
bootstrapScriptContent: `window.___CONTEXT=${JSON.stringify(context)};`,
onAllReady() {
pipe(response);
},
onError(error) {
console.error(error);
},
});

const body = await response.getData();

const head = renderToStaticMarkup(<HeadContent />);
const head = renderToStaticMarkup(<HeadContent headManager={headManager} />);

return { body, head };
}
6 changes: 3 additions & 3 deletions packages/react/src/head-context.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { createContext, ReactElement, ReactNode } from "react";

class HeadProvider {
export class HeadManager {
private head: React.ReactElement[] = [];

private removeTag(tag: string) {
Expand Down Expand Up @@ -31,6 +31,6 @@ class HeadProvider {
}
}

const headProvider = new HeadProvider();
const defaultHeadManager = new HeadManager();

export const HeadContext = createContext(headProvider);
export const HeadContext = createContext(defaultHeadManager);