Created
December 23, 2021 09:22
-
-
Save n1xx1/461e10743796e48b805575d20265fee3 to your computer and use it in GitHub Desktop.
Remix With Helmet and Chakra-UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ChakraProvider } from "@chakra-ui/react"; | |
import { hydrate } from "react-dom"; | |
import { RemixBrowser } from "remix"; | |
hydrate( | |
<ChakraProvider> | |
<RemixBrowser /> | |
</ChakraProvider>, | |
document.getElementById("wrapper") | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ChakraProvider } from "@chakra-ui/react"; | |
import { renderToString } from "react-dom/server"; | |
import { Helmet } from "react-helmet"; | |
import type { EntryContext } from "remix"; | |
import { RemixServer } from "remix"; | |
export default function handleRequest( | |
request: Request, | |
responseStatusCode: number, | |
responseHeaders: Headers, | |
remixContext: EntryContext | |
) { | |
const markup = renderToString( | |
<ChakraProvider> | |
<RemixServer context={remixContext} url={request.url} /> | |
</ChakraProvider> | |
); | |
const helmet = Helmet.renderStatic(); | |
responseHeaders.set("Content-Type", "text/html"); | |
const output = `<!DOCTYPE html> | |
<html lang="en" ${helmet.htmlAttributes.toString()}> | |
<head> | |
<meta charSet="utf-8" /> | |
<meta name="viewport" content="width=device-width,initial-scale=1" /> | |
${helmet.title.toString()} | |
${helmet.meta.toString()} | |
${helmet.link.toString()} | |
${helmet.style.toString()} | |
${helmet.script.toString()} | |
</head> | |
<body ${helmet.bodyAttributes.toString()}> | |
<div id="wrapper">${markup}</div> | |
</body> | |
</html>`; | |
return new Response(output, { | |
status: responseStatusCode, | |
headers: responseHeaders, | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Box, Heading } from "@chakra-ui/react"; | |
import { ReactNode } from "react"; | |
import { | |
Links, | |
LiveReload, | |
Meta, | |
Outlet, | |
Scripts, | |
ScrollRestoration, | |
useCatch, | |
} from "remix"; | |
import { Layout } from "./components/layout"; | |
import { SturdyHelmet } from "./components/sturdy-helmet"; | |
interface DocumentProps { | |
children: ReactNode; | |
title?: string; | |
} | |
function Document({ children, title }: DocumentProps) { | |
return ( | |
<> | |
<SturdyHelmet> | |
{title ? <title>{title}</title> : null} | |
<Meta /> | |
<Links /> | |
<ScrollRestoration /> | |
<Scripts /> | |
<LiveReload /> | |
</SturdyHelmet> | |
{children} | |
</> | |
); | |
} | |
export default function App() { | |
return ( | |
<Document> | |
<Layout> | |
<Outlet /> | |
</Layout> | |
</Document> | |
); | |
} | |
export function ErrorBoundary({ error }: { error: Error }) { | |
return ( | |
<Document title="Error!"> | |
<Box> | |
<Heading as="h1">There was an error</Heading> | |
</Box> | |
</Document> | |
); | |
} | |
export function CatchBoundary() { | |
let caught = useCatch(); | |
return ( | |
<Document title={`${caught.status} ${caught.statusText}`}> | |
<Box> | |
<Heading as="h1"> | |
{caught.status} {caught.statusText} | |
</Heading> | |
</Box> | |
</Document> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Children, | |
cloneElement, | |
Fragment, | |
HTMLAttributes, | |
isValidElement, | |
ReactNode, | |
} from "react"; | |
import { Helmet, HelmetProps } from "react-helmet"; | |
// custom solution to support deep nesting in helmet components | |
export function SturdyHelmet({ children }: HelmetProps) { | |
return <Helmet children={explodeFragment(children, "")} />; | |
} | |
function isReactFragment(node: any) { | |
if (node.type) { | |
return node.type === Fragment; | |
} | |
return node === Fragment; | |
} | |
function explodeFragment(n: ReactNode, prefix: string): ReactNode[] { | |
if (isReactFragment(n)) { | |
return ( | |
Children.toArray((n as any).props.children).flatMap((c, i) => | |
explodeFragment(c, `${prefix}-${i}`) | |
) ?? [] | |
); | |
} | |
if (isValidElement<HTMLAttributes<HTMLElement>>(n)) { | |
// Helmet doesn't support dangerouslySetInnerHTML, but setting the children works fine. | |
if (n.props.dangerouslySetInnerHTML) { | |
return [ | |
cloneElement( | |
n, | |
{ key: prefix }, | |
n.props.dangerouslySetInnerHTML.__html | |
), | |
]; | |
} | |
return [cloneElement(n, { key: prefix })]; | |
} | |
return []; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment