🐞 Remix Streaming SSR #393
Replies: 10 comments
-
@itsMapleLeaf It's possible that the update of Remix in the past few months has caused the Master CSS JIT to not work. |
Beta Was this translation helpful? Give feedback.
-
@itsMapleLeaf We're facing a known issue that cannot be fixed. Just import MasterCSS into import { RemixBrowser } from '@remix-run/react'
import { startTransition, StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { MasterCSS } from '@master/css/dist'
function hydrate() {
startTransition(() => {
new MasterCSS()
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
)
})
}
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(hydrate)
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
setTimeout(hydrate, 1)
} and got the error: Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
at throwOnHydrationMismatch (entry.client-U5T4FRNQ.js:9132:17)
at tryToClaimNextHydratableInstance (entry.client-U5T4FRNQ.js:9153:15)
at updateHostComponent (entry.client-U5T4FRNQ.js:14365:13)
at beginWork (entry.client-U5T4FRNQ.js:15471:22)
at beginWork$1 (entry.client-U5T4FRNQ.js:19251:22)
at performUnitOfWork (entry.client-U5T4FRNQ.js:18696:20)
at workLoopConcurrent (entry.client-U5T4FRNQ.js:18687:13)
at renderRootConcurrent (entry.client-U5T4FRNQ.js:18662:15)
at performConcurrentWorkOnRoot (entry.client-U5T4FRNQ.js:18178:46)
at workLoop (entry.client-U5T4FRNQ.js:200:42) Master CSS's JIT is just a simple JavaScript package; not sure how Remix works with React hydration. Except for Remix, Master CSS's JIT currently works well with other frameworks. |
Beta Was this translation helpful? Give feedback.
-
Hm, I think the hydration issue happens because MasterCSS adds a <style> tag that import { MasterCSS } from "@master/css"
import { useEffect, useLayoutEffect } from "react"
const useIsomorphicEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect
let initialized = false
export function useMasterCSS() {
useIsomorphicEffect(() => {
if (initialized) return
initialized = true
new MasterCSS()
})
} import { useMasterCSS } from "./helpers/master-css"
export default function App() {
useMasterCSS()
// ...
} So this is where hybrid rendering would come in. But I'm not sure how to set up hybrid rendering with streaming SSR 😅 I looked at the NextJS example, but it looks like MCSS expects all of the HTML to be available to generate the styles |
Beta Was this translation helpful? Give feedback.
-
You are a professional! Hybrid Rendering ( AOT + JIT ) and Progressive Rendering ( SSR + JIT ) of Master CSS v2.0 are designed to avoid FOUC for better SEO and loading speed. The way to practice SSR is very simple, but it must rely on the framework's import { renderIntoHTML } from '@master/css'
import { config } from './master.css'
const html: string = `
<html>
<head></head>
<body>
<h1 class="text:center font:32">Hello World</h1>
</body>
</html>
`
const result = renderIntoHTML(html, config) The official Remix guide isn't finished yet, and your attempts hastened its arrival. |
Beta Was this translation helpful? Give feedback.
-
Good to know you're taking a closer look at this! Here's how Remix's default SSR setup works, it uses view codefunction handleBrowserRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
return new Promise((resolve, reject) => {
let didError = false
const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
onShellReady() {
const body = new PassThrough()
responseHeaders.set("Content-Type", "text/html")
resolve(
new Response(body, {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
}),
)
pipe(body)
},
onShellError(err: unknown) {
reject(err)
},
onError(error: unknown) {
didError = true
console.error(error)
},
},
)
setTimeout(abort, ABORT_DELAY)
})
} From what I can tell, the problem with using Does MasterCSS have an API to support SSR with streaming? I think not using |
Beta Was this translation helpful? Give feedback.
-
@itsMapleLeaf The host of Master CSS is basically only the root Therefore, all streaming of Remix after the document may need to go through JIT, and there is currently no CSS architecture that can collect scans across streams and merge them into the current page. We need to provide a |
Beta Was this translation helpful? Give feedback.
-
The SSG stage of Master CSS progressive rendering is mainly used to pre-generate the critical CSS rules required to improve FOUC, FCP, and CLS, also speed up page loading. Any class names generated by client-side dynamic programming should be hydrated by the Master CSS JIT. Is there any reason to pre-generate CSS rules on the server side during the streaming phase after the document is loaded? |
Beta Was this translation helpful? Give feedback.
-
We have to know how Remix reinserts CSS text into the document in case of SSR streaming. |
Beta Was this translation helpful? Give feedback.
-
That makes sense 🤔 The best way I can think of to insert the text into the streamed content is with react context // entry.client.tsx
renderToPipeableStream(
<CssContext.Provider value={cssText}>
<RemixServer />
</CssContext.Provider>
) // root.tsx
export default function App() {
const css = useContext(CssContext)
return (
<html>
<head>
<style dangerouslySetInnerHTML={{ __html: css }} />
</head>
{/* etc. */}
</html>
)
} Also FWIW, a similar library named Twind has a built-in API for this: https://github.com/tw-in-js/twind/blob/main/examples/with-remix_react-v18/app/entry.server.tsx#L26 |
Beta Was this translation helpful? Give feedback.
-
I think we have come to a conclusion, we will try to design related API in Thanks for your enthusiasm! This is very helpful! |
Beta Was this translation helpful? Give feedback.
-
What happened?
StackBlitz: https://stackblitz.com/github/itsMapleLeaf/mastercss-remix-test?file=app%2Fentry.client.tsx,app%2Froot.tsx,app%2Froutes%2Findex.tsx
Before hydration, it briefly shows the styles I would expect (I think?), but after hydration, they all go away, and don't get updated. I think this is unexpected?
Version
v2.0.0-beta.105
Relevant log output
No response
What browsers are you seeing the problem on?
No response
Beta Was this translation helpful? Give feedback.
All reactions