Skip to content

Commit 7f2d028

Browse files
fix(styled-components): fix SSR and error styles (#311)
1 parent 1af4912 commit 7f2d028

File tree

12 files changed

+119
-26
lines changed

12 files changed

+119
-26
lines changed

styled-components/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ node_modules
44
/build
55
/public/build
66
.env
7+
/app/components

styled-components/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ Open this example on [CodeSandbox](https://codesandbox.com):
1010

1111
## Example
1212

13-
This example shows how to use Styled Components with Remix. Relevant files:
13+
To support server-side rendering without hydration errors, [Styled Components uses a Babel plugin to ensure consistent class names across server and client.](https://styled-components.com/docs/advanced#tooling-setup) In this example the source code for our styled components is in `components/src`. This is compiled with the Babel CLI (to generate JavaScript files) and `tsc` (to generate `.d.ts` files), co-ordinated using [npm-run-all](https://www.npmjs.com/package/npm-run-all). The output from both of these tools is generated in `app/components` which is ignored by Git. The app can then import these components from `~/components/*`.
14+
15+
One notable aspect of Styled Components that we need to manage is the way in which it injects styles into the `head` element. This clashes with the model of using React to render the entire document from the root because Styled Components doesn't expect the `head` element to be remounted. When this happens, any CSS that Styled Components thinks is in the document is actually removed, resulting in a loss of styling. In order to avoid this issue when navigating between error routes and non-error routes, we need to wrap the entire app in a pathless route which in this example we're calling `__boundary`. This allows us to handle top-level errors without re-mounting the entire app.
16+
17+
## Relevant files
1418

1519
- [app/root.tsx](./app/root.tsx) - This is where we render the app and if we're rendering on the server we have placeholder text of `__STYLES__`.
1620
- [app/entry.server.tsx](./app/entry.server.tsx) - This is where we render the app on the server and replace `__STYLES__` with the styles that styled-components collect.
17-
- [app/routes/index.tsx](./app/routes/index.tsx) - Here's where we use the `styled` function to create a styled component.
21+
- [app/routes/__boundary.tsx](./app/routes/__boundary.tsx) - The top-level error boundary for the app to avoid re-mounting the document.
22+
- [app/routes/__boundary/\$.tsx](./app/routes/__boundary/$.tsx) - The top-level splat route that manually throws a 404 response so we can catch it in `__boundary.tsx`.
23+
- [components/src/Box.tsx](./components/src/Box.tsx) - An example `styled` component.
1824

1925
## Related Links
2026

styled-components/app/entry.client.tsx

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Outlet, useCatch } from "@remix-run/react";
2+
3+
import { Box } from "~/components/Box";
4+
5+
export default function Boundary() {
6+
return <Outlet />;
7+
}
8+
9+
export function CatchBoundary() {
10+
const caught = useCatch();
11+
12+
return (
13+
<Box>
14+
<h1>Catch Boundary</h1>
15+
<p>
16+
{caught.status} {caught.statusText}
17+
</p>
18+
</Box>
19+
);
20+
}
21+
22+
export function ErrorBoundary({ error }: { error: Error }) {
23+
return (
24+
<Box>
25+
<h1>Error Boundary</h1>
26+
<p>{error.message}</p>
27+
<pre>{error.stack}</pre>
28+
</Box>
29+
);
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Any request for this top-level splat route is a 404
2+
export const loader = () => {
3+
throw new Response(null, {
4+
status: 404,
5+
statusText: "Not Found",
6+
});
7+
};
8+
9+
// This is needed to tell Remix it isn't a resource route
10+
export default () => null;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ErrorComponent() {
2+
throw new Error("This route throws on render!");
3+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Link } from "@remix-run/react";
2+
3+
import { Box } from "~/components/Box";
4+
5+
export default function Index() {
6+
return (
7+
<Box>
8+
<h1>Welcome to Remix With Styled Components</h1>
9+
<ul>
10+
<li>
11+
<Link to="/error">Error</Link>
12+
</li>
13+
<li>
14+
<Link to="/404">404</Link>
15+
</li>
16+
</ul>
17+
</Box>
18+
);
19+
}

styled-components/app/routes/index.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.

styled-components/components/.babelrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"presets": [
3+
"@babel/preset-typescript",
4+
["@babel/preset-react", { "runtime": "automatic" }]
5+
],
6+
"plugins": ["babel-plugin-styled-components"],
7+
"sourceMaps": "inline"
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import styled from "styled-components";
2+
3+
export const Box = styled("div")`
4+
font-family: system-ui, sans-serif;
5+
line-height: 1.4;
6+
`;

0 commit comments

Comments
 (0)