styled-components fails in Facebook and TikTok browser
Closed this issue ยท 10 comments
What version of Remix are you using?
1.11.0
Steps to Reproduce
- run
npx create-remix@latest project-name
- select:
basic
,typescript
,netlify
andn
so we can run yarn - run
yarn add styled-components
- run
yarn add -D @types/styled-components
- create a basic styled component
import styled from 'styled-components'
const H1 = styled.h1`
border: 1px solid red;
`
export default function Index() {
return (
<div>
<H1>test</H1>
</div>
)
}
update entry.server.tsx
with:
import { renderToString } from "react-dom/server";
import { RemixServer } from "@remix-run/react";
import type { EntryContext } from "@remix-run/node"; // or cloudflare/deno
import { ServerStyleSheet } from "styled-components";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const sheet = new ServerStyleSheet();
let markup = renderToString(
sheet.collectStyles(
<RemixServer
context={remixContext}
url={request.url}
/>
)
);
const styles = sheet.getStyleTags();
markup = markup.replace("__STYLES__", styles);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
inside root.tsx
file update the head tag to include styles:
<head>
<Meta />
<Links />
{typeof document === "undefined"
? "__STYLES__"
: null}
</head>
and deploy to netlify.
Here is an example project that replicates the issue:
https://github.com/marianzburlea/test-sc
and here is an example of the styled component receiving a red border.
https://steady-pixie-99fc00.netlify.app/
Expected Behavior
actual styles to be displayed
Actual Behavior
hydration fails and no styles are applied, when I click on a link from Facebook and Tiktok.
The BIG problem is not facebook messenger, it's how people that click paid ads see the website.
This is the same way users that click on an ad, end up on the website, with no styles.
Im experiencing similar issues outside of the context you describe.
Basically any hydration error where react is forced to rebuild the <head>
will cause the styles of styled-components to be removed. While react itself recovers from this state, styled-components fails to do so.
As mentioned in #5159 I'd say this is not per se a remix bug but an architectural incompatibility as the issue can be reproduced with any react app failing to hydrate the whole document and might also happen to other libraries that inject things into the DOM.
related issue in react: facebook/react#24430
I was able to work around the problem by:
- downgrading to react@17
- ensuring that
{typeof document === "undefined" ? "__STYLES__" : null}
is rendered at the very end of the<head>
Would still love to use latest react features within my app.
+1 to this issue - Kent has responded to this issue here, stating that it is a React issue, which I think is fair enough, but I'm not sure if or when the React team would be doing anything about it.
There's a potential workaround, but it's not great - you can render Remix not at the document level, but within a <div>
in the body.
By wrapping in an HTML container you can solve this issue, however it comes at the cost of not being able to use <Meta>
or <Links>
(mostly).
You can somewhat work around this, only sending Links and Meta on the server render and forgoing meta and link changes on the client routing.
entry.client.tsx:
import { RemixBrowser } from '@remix-run/react';
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root')!, <RemixBrowser />);
entry.server.tsx (an example using React 18 streams):
import { ServerRoot } from './root.server';
export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
const stream = await renderToReadableStream(
<ServerRoot
location={remixContext.staticHandlerContext.location}
loaderData={remixContext.staticHandlerContext.loaderData}
routeModules={remixContext.routeModules}
matches={remixContext.staticHandlerContext.matches}
>
<RemixServer context={remixContext} url={request.url} />
</ServerRoot>,
);
return new Response(stream, {
status: responseStatusCode,
headers: responseHeaders,
});
}
ServerRoot.tsx:
const ServerRoot = ({
children,
...rest
}) => {
return (
<html lang="en">
<head>
<V1Meta {...rest} />
</head>
<body>
<div id="root">
{children}
</div>
</body>
</html>
);
};
Where V1Meta is the Meta component from Remix, but takes the params from props rather than hooks (as the context is not created yet)
The root.tsx
does not contain any <html>
tags and doesn't play allow any information in the head
to be changed client-side.
Thanks for sharing. I was also tinkering around with rendering the remix app into a sub-element of the page but gave up because it caused too many things I love about remix to stop working.
As with the "who's problem is this": Yeah the issue lays between React and Styled Components. But Remix on the one hand made the architectural decision to work on the whole document which surfaces the problem and on the other hand advertises styled-components to work in remix apps (which it does but at the cost of react@18).
@kiliman figured out how to do this with the current tools and for me it solves the styled-components issues: #5244 (comment)
This issue has been automatically marked stale because we haven't received a response from the original author in a while ๐. This automation helps keep the issue tracker clean from issues that are not actionable. Please reach out if you have more information for us or you think this issue shouldn't be closed! ๐ If you don't do so within 7 days, this issue will be automatically closed.
This issue has been automatically closed because we didn't hear anything from the original author after the previous notice.