Replies: 2 comments
-
Thank you for the guideline much appreciated, the way I have implemented it is as below, the problem I'm facing is the stylesheet conflicting with tailwind library. I have tried without tailwind but have not been able to get the same result as Create the application layout import type { LoaderArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { getUser } from "~/session.server";
import stylesheet from "~/tailwind.css";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
];
import {
FluentProvider,
webLightTheme,
} from "@fluentui/react-components";
export const loader = async ({ request }: LoaderArgs) => {
return json({ user: await getUser(request) });
};
export default function App() {
return (
<html lang="en" className="h-full">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body className="h-full">
<FluentProvider theme={webLightTheme}>
<Outlet />
</FluentProvider>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
} I would like to know what's the best approach to layout things. Thanks |
Beta Was this translation helpful? Give feedback.
0 replies
-
This was really helpful, thank you!! |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Last year, with the release of React 18, a series of trendy APIs were introduced, providing possibilities for rendering larger-scale web applications. However, these cutting-edge technologies also brought many challenges, particularly in tasks related to server-side rendering. Despite the React 18 Work Group and extensive discussions among developers from various domains during the development process, the ambitious nature of the changes resulted in many ecosystem components still not being adapted even after a year. In contrast, many meta-frameworks quickly caught up, implementing a series of officially recommended best practices. This created a certain division, causing various UI component libraries to have issues rendering correctly on the client-side. In this article, we will take Microsoft's Fluent UI V9 as an example and provide a brief overview of the tasks developers can undertake during this transition period to ensure compatibility between Remix.run and their component libraries.
The Renderer
One significant characteristic of Remix is its tightly encapsulated component structure, which prevents developers from directly modifying the behavior of the bundler or certain components. For example, many component libraries require a renderer to collect all CSS rules generated during the rendering process. Developers need to wrap the renderer's corresponding context around the component to ensure it can gather all rendering context information. Since this component is specific to the server-side, this work needs to be done in the Node Server script.
However, if you have used Remix, you may find this challenging because the server-side rendering logic is encapsulated within the
<RemixServer />
component. It contains special logic related to server-side rendering and exception handling. Inside this component, there is only the client-side root component.Of course, we can attempt to wrap the SSR-related context components outside the
RemixServer
, but this is not always effective. For example, in Fluent UI V9, there are two providers:RendererProvider
andSSRProvider
. If developers try to wrap them outside theRemixServer
, it will result in errors during server-side rendering.In such cases, we need to create an empty React Context and pass this renderer component to the client-side script. For example:
Next, we need to modify the
entry.server.tsx
file to ensure that your Context wraps around the RemixServer component. Since our Context doesn't have any side effects, it is safe to wrap it here.Then, inside the client-side script, we can wrap the necessary components. However, please note that we don't want these two components to appear in the client-side script. So, we need to find a clever workaround to isolate this part of the work. Remix provides a feature for this purpose: if a JavaScript file's name includes
.server.tsx
, it won't be included in the client-side script bundle. Therefore, leveraging this feature, we can create a new file calledfluent.server.tsx
and write the following component:Next, we create a corresponding client-side component in
root.tsx
:Finally, wrap all the content inside the
<body>
tag with theFluentServerWrapper
. With this, the injection of the Context is completed.Downgrade the streaming rendering process
React 18 introduces a new method for streaming rendering of the virtual DOM called
renderToReadableStream
. However, many CSS-in-JS solutions do not support this approach because they assume that the virtual DOM must be fully rendered before the final CSS rendering can take place. If we use the streaming generation method to output HTML, we will find that the generated stylesheet contains no information. This is because stylesheets typically appear in the<head>
tag, and at the point where the virtual DOM is generated, no components have been rendered yet, so no content is available.To address this issue, we need to downgrade the server-side rendering method until our frontend framework supports the corresponding functionality. Here, we open the
entry.server.tsx
file and make the following changes:Style sheet injection
Both Remix.run and Next.js take control of the complete DOM tree generation at the React level. However, Remix.run does not provide an API for injecting style sheets, so we need to manually manipulate the HTML.
For the server-side, we need to prepare a marker to search for and replace the generated style sheet tag. Here's how it can be done:
Add a new component in fluent.server.tsx:
In the entry.server.tsx file, we will search for this marker and replace it with the generated style sheet:
However, simply doing that is not enough because we may encounter inconsistencies between client-side and server-side rendering. Unlike the old version of the server-side rendering API, for the
hydrateRoot
function, if React detects a mismatch between the client's virtual DOM and the HTML returned by the server, it will throw an error and refuse to proceed with rendering. Therefore, on the client-side, we need to create a component to "reconcile" the server-side rendering result. The specific component should look like this:Please note that
useConstant
is not a built-in React component; you need to install it from NPM. This component performs a simple task: scanning the<head>
section of the HTML document, finding<style>
tags that meet certain conditions, and generating the corresponding virtual DOM elements. For Fluent UI, the filtering criterion is that the component has thedata-make-styles-bucket attribute
. However, style sheet tags in other frameworks may have different characteristics, so developers need to design different filtering criteria based on their own situation.Next, let's build a component that works differently on the client-side and server-side using the same technique:
Finally, we just need to place this component in any location within the
<head />
tag to ensure proper hydration execution.Downgrading the Transition Hydration Process
Another new feature provided by React 18 is Transition, which allows breaking down large tasks into smaller microtasks and arranging them in a priority queue. This mechanism helps the client-side respond more quickly to user input: when a user action occurs, the corresponding asynchronous task is immediately prioritized in the async queue to ensure an immediate response.
The hydration process also adapts to this mechanism. The traditional hydration process is blocking, which means that until hydration is complete, users cannot perform any actions, and the interface appears to be stuck. However, the new version of React improves this process by transforming hydration into a streaming process. React completes HTML parsing and event binding while waiting for user input. If a user triggers an event, the hydration process is immediately suspended until the task is completed before continuing with hydration.
This brings significant performance advantages but also introduces potential issues. For example, Fluent UI uses
Tabster
to handle accessibility tasks such as focus management and keyboard navigation, but this library itself modifies the DOM structure.In the traditional hydration flow, React must complete hydration before
Tabster
gets involved in its processing, including modifying the DOM. However, the new hydration mechanism disrupts this assumption.Tabster
is invoked during component hydration. Once it modifies the DOM structure, subsequent hydration work will encounter errors due to DOM inconsistencies, leading to crashes in the client-side application.To address this issue, we need to downgrade the hydration method on the client-side. The approach is straightforward. Open
entry.client.tsx
and remove thestartTransition
call. The entire file will look like this:With this, the hydration process should no longer encounter issues.
Conclusion
For the foreseeable future, it may be challenging for major component libraries to adapt to the extensive architectural changes introduced by React 18. Some CSS-in-JS solutions may even terminate their development efforts due to being too "dynamic" to be "statically analyzed." This has a significant impact on downstream developers as well. The hope is that these simple experiences can help developers smoothly navigate through this turbulent transition period. Additionally, I wish for the entire React ecosystem to quickly adapt to this "architecture earthquake" and once again provide developers with a smooth development experience.
Beta Was this translation helpful? Give feedback.
All reactions