diff --git a/.changeset/single-fetch-spa-mode.md b/.changeset/single-fetch-spa-mode.md new file mode 100644 index 00000000000..1cab92c1147 --- /dev/null +++ b/.changeset/single-fetch-spa-mode.md @@ -0,0 +1,5 @@ +--- +"@remix-run/dev": patch +--- + +Fix SPA mode when single fetch is enabled by using streaming entry.server diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts index eabd6edbb00..7eeded3c271 100644 --- a/packages/remix-dev/cli/commands.ts +++ b/packages/remix-dev/cli/commands.ts @@ -303,7 +303,8 @@ export async function generateEntry( let defaultEntryClient = path.resolve(defaultsDirectory, "entry.client.tsx"); let defaultEntryServer = path.resolve( defaultsDirectory, - ctx?.remixConfig.ssr === false + ctx?.remixConfig.ssr === false && + ctx?.remixConfig.future.unstable_singleFetch !== true ? `entry.server.spa.tsx` : `entry.server.${serverRuntime}.tsx` ); diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts index ac576095f40..4ddf67780df 100644 --- a/packages/remix-dev/config.ts +++ b/packages/remix-dev/config.ts @@ -469,7 +469,7 @@ export async function resolveConfig( let pkgJson = await PackageJson.load(rootDirectory); let deps = pkgJson.content.dependencies ?? {}; - if (isSpaMode) { + if (isSpaMode && appConfig.future?.unstable_singleFetch != true) { // This is a super-simple default since we don't need streaming in SPA Mode. // We can include this in a remix-spa template, but right now `npx remix reveal` // will still expose the streaming template since that command doesn't have diff --git a/packages/remix-dev/config/defaults/entry.server.node.tsx b/packages/remix-dev/config/defaults/entry.server.node.tsx index 5005bc69178..65ad16eb8b0 100644 --- a/packages/remix-dev/config/defaults/entry.server.node.tsx +++ b/packages/remix-dev/config/defaults/entry.server.node.tsx @@ -15,7 +15,10 @@ export default function handleRequest( remixContext: EntryContext, loadContext: AppLoadContext ) { - return isBotRequest(request.headers.get("user-agent")) + let prohibitOutOfOrderStreaming = + isBotRequest(request.headers.get("user-agent")) || remixContext.isSpaMode; + + return prohibitOutOfOrderStreaming ? handleBotRequest( request, responseStatusCode, diff --git a/templates/spa/app/entry.client.tsx b/templates/spa/app/entry.client.tsx index 999c0a128c1..94d5dc0de0f 100644 --- a/templates/spa/app/entry.client.tsx +++ b/templates/spa/app/entry.client.tsx @@ -1,3 +1,9 @@ +/** + * By default, Remix will handle hydrating your app on the client for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.client + */ + import { RemixBrowser } from "@remix-run/react"; import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; diff --git a/templates/spa/app/entry.server.tsx b/templates/spa/app/entry.server.tsx deleted file mode 100644 index fc66fc858c7..00000000000 --- a/templates/spa/app/entry.server.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { EntryContext } from "@remix-run/node"; -import { RemixServer } from "@remix-run/react"; -import { renderToString } from "react-dom/server"; - -export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext -) { - let html = renderToString( - - ); - html = "\n" + html; - return new Response(html, { - headers: { "Content-Type": "text/html" }, - status: responseStatusCode, - }); -}