diff --git a/docs/src/pages/en/(pages)/framework/http.mdx b/docs/src/pages/en/(pages)/framework/http.mdx index 2f8703f..ac66bc0 100644 --- a/docs/src/pages/en/(pages)/framework/http.mdx +++ b/docs/src/pages/en/(pages)/framework/http.mdx @@ -60,7 +60,7 @@ export default function MyComponent() { ## Response -With `useResponse()` you can get access to the full HTTP response. +With `useResponse()` you can get access to the full HTTP response. This is only available after the response has been sent to the client, in a React component which was suspended and streamed to the client later than the response was sent. ```jsx import { useResponse } from "@lazarv/react-server"; @@ -143,7 +143,7 @@ export default function MyComponent() { }; ``` -You can also modify the headers of the current response. +You can also modify the headers of the current response by passing an object of key-value pairs: ```jsx import { headers } from "@lazarv/react-server"; @@ -157,6 +157,50 @@ export default function MyComponent() { }; ``` +Or by passing a `Headers` object: + +```jsx +import { headers } from "@lazarv/react-server"; + +export default function MyComponent() { + headers(new Headers({ + "X-My-Header": "My value", + })); + + return
Headers: {JSON.stringify(headers())}
; +}; +``` + +Or by passing an array of key-value pairs: + +```jsx +import { headers } from "@lazarv/react-server"; + +export default function MyComponent() { + headers([ + ["X-My-Header", "My value"], + ]); + + returnHeaders: {JSON.stringify(headers())}
; +}; +``` + +Modifying the headers with the `headers()` function will override the headers of the current response. If you want to mutate the response headers directly, you can use three addition helper functions to set, append or delete headers. These functions are `setHeader()`, `appendHeader()` and `deleteHeader()`. + +```jsx +import { setHeader, appendHeader, deleteHeader } from "@lazarv/react-server"; + +export default function MyComponent() { + setHeader("X-My-Header", "My first value"); + appendHeader("X-My-Header", "My second value"); + deleteHeader("X-My-Header"); + + returnCheck the response headers!
; +} +``` + +> **Note:** Keep in mind that HTTP headers are case-insensitive! + ## Cookies @@ -277,3 +321,47 @@ export default function MyComponent() { returnOutlet: {outlet}
; } ``` + + +## Render Lock + + +With `useRender()` you can get access to the render lock of the current request. This is useful if you want to lock rendering of a React Server Component while the async function is running or until the lock is released, as React Server Components are rendered using streaming by default. This is especially useful for handling HTTP headers and cookies in an async React Server Component. Without locking the rendering, the headers and cookies will be sent to the client before the async function is finished. When a lock is detected in the rendering process, the rendering will wait for the lock to be released before beginning to send the headers and cookies to the client and starting the streaming of the React Server Component. + +```jsx +import { headers, useRender } from "@lazarv/react-server"; + +export default function MyComponent() { + const { lock } = useRender(); + + await lock(async () => { + // Do something async + await new Promise((resolve) => setTimeout(resolve, 1000)); + headers({ + "x-lock": "works", + }); + }); + + returnRender lock
; +} +``` + +You can also use the `lock()` function to get an `unlock()` function to release the lock later. + +```jsx +import { headers, useRender } from "@lazarv/react-server"; + +export default function MyComponent() { + const { lock } = useRender(); + + const unlock = lock(); + // Do something async + await new Promise((resolve) => setTimeout(resolve, 1000)); + headers({ + "x-lock": "works", + }); + unlock(); + + returnRender lock
; +} +``` diff --git a/packages/react-server/lib/build/server.mjs b/packages/react-server/lib/build/server.mjs index 2856a95..471478d 100644 --- a/packages/react-server/lib/build/server.mjs +++ b/packages/react-server/lib/build/server.mjs @@ -153,7 +153,11 @@ export default async function serverBuild(root, options) { entryFileNames: "[name].mjs", chunkFileNames: "server/[name].[hash].mjs", manualChunks: (id, ...rest) => { - if (id.includes("@lazarv/react-server") && id.endsWith(".mjs")) { + if ( + (id.includes("@lazarv/react-server") || + id.includes(sys.rootDir)) && + id.endsWith(".mjs") + ) { return "@lazarv/react-server"; } return ( diff --git a/packages/react-server/lib/dev/ssr-handler.mjs b/packages/react-server/lib/dev/ssr-handler.mjs index 950d4da..037d876 100644 --- a/packages/react-server/lib/dev/ssr-handler.mjs +++ b/packages/react-server/lib/dev/ssr-handler.mjs @@ -23,6 +23,7 @@ import { MEMORY_CACHE_CONTEXT, MODULE_LOADER, REDIRECT_CONTEXT, + RENDER, RENDER_CONTEXT, RENDER_STREAM, SERVER_CONTEXT, @@ -109,6 +110,7 @@ export default async function ssrHandler(root) { const renderContext = createRenderContext(httpContext); context$(RENDER_CONTEXT, renderContext); + context$(RENDER, render); try { const middlewares = await root_init$?.(); @@ -132,6 +134,7 @@ export default async function ssrHandler(root) { } const styles = collectStylesheets?.(rootModule) ?? []; + styles.unshift(...(getContext(STYLES_CONTEXT) ?? [])); context$(STYLES_CONTEXT, styles); await module_loader_init$?.(ssrLoadModule, moduleCacheStorage); diff --git a/packages/react-server/lib/handlers/error.mjs b/packages/react-server/lib/handlers/error.mjs index 45ddab3..d0b8dbf 100644 --- a/packages/react-server/lib/handlers/error.mjs +++ b/packages/react-server/lib/handlers/error.mjs @@ -101,12 +101,12 @@ function plainResponse(e) { status: 500, statusText: "Internal Server Error", }; + const headers = getContext(HTTP_HEADERS) ?? new Headers(); + headers.set("Content-Type", "text/plain; charset=utf-8"); + return new Response(e?.stack ?? null, { ...httpStatus, - headers: { - "Content-Type": "text/plain; charset=utf-8", - ...(getContext(HTTP_HEADERS) ?? {}), - }, + headers, }); } @@ -130,6 +130,9 @@ export default async function errorHandler(err) { statusText: "Internal Server Error", }; + const headers = getContext(HTTP_HEADERS) ?? new Headers(); + headers.set("Content-Type", "text/html; charset=utf-8"); + const error = await prepareError(err); const viteDevServer = getContext(SERVER_CONTEXT); @@ -163,10 +166,7 @@ export default async function errorHandler(err) {