diff --git a/runtime/desktop/frameworks.md b/runtime/desktop/frameworks.md index 3f69520bb..21fd9cdc3 100644 --- a/runtime/desktop/frameworks.md +++ b/runtime/desktop/frameworks.md @@ -1,7 +1,7 @@ --- -last_modified: 2026-06-25 +last_modified: 2026-06-27 title: "Frameworks" -description: "Run Next.js, Astro, Fresh, Remix, Nuxt, SvelteKit, SolidStart, TanStack Start, and Vite projects as desktop apps with no code changes." +description: "Run Next.js, Astro, Fresh, Remix, React Router, Nuxt, SvelteKit, SolidStart, TanStack Start, and Vite projects as desktop apps." --- :::info Available in Deno 2.9 @@ -17,12 +17,13 @@ framework's production server (or dev server under `--hmr`) with the webview pointed at it. ```sh -# Inside a Next.js / Astro / Fresh / etc. project: +# Inside a Next.js / Astro / Fresh / React Router / etc. project: deno desktop . ``` -No code changes, no special adapter. The same project that runs as a web app -ships as a desktop app. +Most supported frameworks need no special adapter. Some frameworks have +runtime-specific entry files; check the per-framework notes below before +building. ## Detection @@ -35,6 +36,7 @@ match wins. | Astro | `astro.config.{mjs,ts,js}` | | Fresh | `fresh.gen.ts` or `_fresh/` directory | | Remix | `@remix-run/react` or `@remix-run/dev` in `package.json` | +| React Router | `@react-router/dev` in `package.json` | | Nuxt | `nuxt.config.{ts,js,mjs}` | | SvelteKit | `svelte.config.{js,ts}` | | SolidStart | `@solidjs/start` in `package.json` | @@ -112,6 +114,75 @@ deno desktop . Production: runs `remix-serve` against the `build/` directory. Dev (under `--hmr`): `@remix-run/dev` CLI. +### React Router + +React Router framework mode is detected by `@react-router/dev` in +`package.json`. Both SPA mode (`ssr: false`) and server-side rendering are +supported once the project builds successfully with Deno. + +React Router's default server entry targets Node.js and uses +`renderToPipeableStream` from `react-dom/server`. Deno resolves +`react-dom/server` to a Web Streams build that uses `renderToReadableStream` +instead. Add a Deno-compatible `app/entry.server.tsx` before running +`react-router build`; React Router also uses this file to prerender the SPA +fallback when `ssr: false`. + +```tsx title="app/entry.server.tsx" +import type { EntryContext } from "react-router"; +import { ServerRouter } from "react-router"; +import { renderToReadableStream } from "react-dom/server"; +import { isbot } from "isbot"; + +export default async function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + routerContext: EntryContext, +) { + if (request.method.toUpperCase() === "HEAD") { + return new Response(null, { + status: responseStatusCode, + headers: responseHeaders, + }); + } + + let statusCode = responseStatusCode; + + const body = await renderToReadableStream( + , + { + signal: request.signal, + onError(error: unknown) { + statusCode = 500; + console.error(error); + }, + }, + ); + + if ( + isbot(request.headers.get("user-agent") || "") || routerContext.isSpaMode + ) { + await body.allReady; + } + + responseHeaders.set("Content-Type", "text/html"); + return new Response(body, { + headers: responseHeaders, + status: statusCode, + }); +} +``` + +Then build and package the app: + +```sh +deno task build +deno desktop . +``` + +Production serves static client assets from `build/client` and, for SSR +projects, routes requests through `build/server/index.js`. + ### Nuxt ```sh @@ -147,8 +218,8 @@ Both use the Nitro framework underneath; detection handles them via the Vite projects are detected by a `vite.config.*` file or a `vite` dependency. This sits at the lowest bundler priority, so the meta-frameworks built on Vite -(Astro, SvelteKit, Nuxt, Remix, SolidStart, TanStack Start) are matched by their -own config or dependency first. +(Astro, SvelteKit, Nuxt, Remix, React Router, SolidStart, TanStack Start) are +matched by their own config or dependency first. - **SSR** (a `server.{ts,js,mjs}` entry alongside `vite.config.*`): the SSR entry runs directly in production, and dev (under `--hmr`) runs the Vite dev diff --git a/runtime/desktop/index.md b/runtime/desktop/index.md index 668876bc6..fe3de2c39 100644 --- a/runtime/desktop/index.md +++ b/runtime/desktop/index.md @@ -1,5 +1,5 @@ --- -last_modified: 2026-06-25 +last_modified: 2026-06-27 title: "Desktop apps" description: "Build self-contained desktop applications from a Deno project, with framework auto-detection, hot reload, native windowing, auto-update, and cross-platform distribution." --- @@ -32,10 +32,10 @@ integration. the bundled Chromium (CEF) backend when you need identical rendering across macOS, Windows, and Linux. - **Framework auto-detection.** Point `deno desktop` at a Next.js, Astro, Fresh, - Remix, Nuxt, SvelteKit, SolidStart, TanStack Start, or Vite SSR project and it - runs: the production server in release mode, the dev server with hot reload - under `--hmr`. No code changes are required to take an existing web project to - the desktop. + Remix, React Router, Nuxt, SvelteKit, SolidStart, TanStack Start, or Vite SSR + project and it runs: the production server in release mode, the dev server + with hot reload under `--hmr`. Most frameworks need no special adapter; see + [Frameworks](/runtime/desktop/frameworks/) for per-framework requirements. - **In-process bindings instead of IPC.** Backend and UI communication goes through in-process channels, not socket-based IPC. Values are still encoded as they cross the call boundary, but there is no cross-process round-trip between diff --git a/runtime/reference/cli/compile.md b/runtime/reference/cli/compile.md index 6b66fe4f6..268790c9e 100644 --- a/runtime/reference/cli/compile.md +++ b/runtime/reference/cli/compile.md @@ -1,5 +1,5 @@ --- -last_modified: 2026-06-25 +last_modified: 2026-06-27 title: "deno compile" oldUrl: - /runtime/manual/tools/compile/ @@ -43,6 +43,7 @@ Supported frameworks: - Astro - Fresh (1.x and 2.x) - Remix +- React Router - SvelteKit - Nuxt - SolidStart @@ -50,7 +51,7 @@ Supported frameworks: - Vite (SSR, plus SPA/MPA projects served as static output) ```sh -# In a Next.js / Astro / Fresh / etc. project +# In a Next.js / Astro / Fresh / React Router / etc. project deno compile . # Or pointing at a specific app directory @@ -61,6 +62,12 @@ Generated entrypoints use `import.meta.dirname` so framework asset paths resolve correctly against the [virtual filesystem](#including-data-files-or-directories) inside the compiled binary. +React Router projects must build successfully under Deno before they can be +compiled. Because React Router's default server entry targets Node.js, add a Web +Streams-compatible `app/entry.server.tsx` when building with Deno. See the +[React Router desktop framework notes](/runtime/desktop/frameworks/#react-router) +for an example. + If the project doesn't match any supported framework, `deno compile` will error out. diff --git a/runtime/reference/cli/desktop.md b/runtime/reference/cli/desktop.md index baac69d38..4310401b3 100644 --- a/runtime/reference/cli/desktop.md +++ b/runtime/reference/cli/desktop.md @@ -1,5 +1,5 @@ --- -last_modified: 2026-06-25 +last_modified: 2026-06-27 title: "deno desktop" openGraphLayout: "/open_graph/cli-commands.jsx" openGraphTitle: "deno desktop" @@ -24,9 +24,10 @@ deno desktop --output MyApp.app main.ts ``` The entrypoint is optional. A bare `deno desktop` (or `deno desktop .`) detects -a supported framework (Next.js, Astro, Fresh, and others) in the current -directory and builds it without any code changes. See -[Frameworks](/runtime/desktop/frameworks/). +a supported framework (Next.js, Astro, Fresh, React Router, and others) in the +current directory and builds the appropriate desktop entrypoint. See +[Frameworks](/runtime/desktop/frameworks/) for supported frameworks and +per-framework requirements. This page covers the command-line flags. For the full guide (backends, [`Deno.BrowserWindow`](/api/deno/~/Deno.BrowserWindow), bindings, auto-update,