Skip to content

Commit

Permalink
perf: enable remix single fetch + native web streams 🚀
Browse files Browse the repository at this point in the history
  • Loading branch information
Dabolus committed Apr 25, 2024
1 parent a11cbbd commit db26cbf
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 8 deletions.
45 changes: 39 additions & 6 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderToString } from 'react-dom/server';
// Use the server.browser package to be able to use renderToReadableStream also on Node.js/Bun
import { renderToReadableStream } from 'react-dom/server.browser';
import { RemixServer } from '@remix-run/react';
import type { EntryContext } from '@remix-run/node';
import {
Expand All @@ -16,6 +17,9 @@ import PageStyles from './src/PageStyles';
import i18next, { localesDirectory } from './i18next.server';
import i18n from './i18n';

// Reject all pending promises from handler functions after 5 seconds
export const streamTimeout = 5000;

export default async function handleRequest(
request: Request,
responseStatusCode: number,
Expand Down Expand Up @@ -45,18 +49,47 @@ export default async function handleRequest(
<I18nextProvider i18n={i18nextInstance}>
<CacheProvider value={cache}>
<CssVarsProvider theme={theme} defaultMode="system">
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<PageStyles />
<RemixServer context={remixContext} url={request.url} />
<RemixServer
context={remixContext}
url={request.url}
abortDelay={streamTimeout * 2}
/>
</CssVarsProvider>
</CacheProvider>
</I18nextProvider>
);
}

// Render the component to a string.
const html = renderToString(<MuiRemixServer />);
const controller = new AbortController();
let timeout = setTimeout(() => controller.abort(), streamTimeout * 2);

const stream = await renderToReadableStream(<MuiRemixServer />, {
signal: controller.signal,
});
try {
await stream.allReady;
clearTimeout(timeout);
} catch (error) {
responseStatusCode = 500;
console.error(error);
}

// Read the stream to a string
const reader = stream.getReader();
const decoder = new TextDecoder();
let html = '<!DOCTYPE html>';
await reader.read().then(function processText({
done,
value,
}: ReadableStreamReadResult<Uint8Array>): void | Promise<void> {
if (done) {
return;
}
html += decoder.decode(value);
return reader.read().then(processText);
});

// Grab the CSS from emotion
const { styles } = extractCriticalToChunks(html);
Expand All @@ -77,7 +110,7 @@ export default async function handleRequest(

responseHeaders.set('Content-Type', 'text/html');

return new Response(`<!DOCTYPE html>${markup}`, {
return new Response(markup, {
status: responseStatusCode,
headers: responseHeaders,
});
Expand Down
4 changes: 4 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
/// <reference types="vite/client" />
/// <reference types="@remix-run/node" />

declare module 'react-dom/server.browser' {
export * from 'react-dom/server';
}
7 changes: 6 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"include": ["env.d.ts", "**/*.ts", "**/*.tsx"],
"include": [
"env.d.ts",
"**/*.ts",
"**/*.tsx",
"node_modules/@remix-run/react/future/single-fetch.d.ts"
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"isolatedModules": true,
Expand Down
9 changes: 8 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import tsconfigPaths from 'vite-tsconfig-paths';
installGlobals({ nativeFetch: true });

export default defineConfig(({ mode }) => ({
plugins: [remix(), tsconfigPaths()],
plugins: [
remix({
future: {
unstable_singleFetch: true,
},
}),
tsconfigPaths(),
],
build: {
sourcemap: true,
rollupOptions: {
Expand Down

0 comments on commit db26cbf

Please sign in to comment.