Remix
A guide for installing Saas UI with Remix projects
1. Installation#
Inside your Remix
project root directory, install Saas UI by running either
of the following:
npm i @saas-ui/react @chakra-ui/react @chakra-ui/next-js @emotion/react @emotion/styled framer-motion
yarn add @saas-ui/react @chakra-ui/react @chakra-ui/next-js @emotion/react @emotion/styled framer-motion
pnpm add @saas-ui/react @chakra-ui/react @chakra-ui/next-js @emotion/react @emotion/styled framer-motion
2. Provider Setup#
To prevent loss of styles we need to do some changes on the server-side and client-side.
We’ll create a context.tsx
in the app
folder.
// context.tsximport React, { createContext } from 'react'export interface ServerStyleContextData {key: stringids: Array<string>css: string}export const ServerStyleContext = createContext<ServerStyleContextData[] | null>(null)export interface ClientStyleContextData {reset: () => void}export const ClientStyleContext = createContext<ClientStyleContextData | null>(null)
Next on the agenda is to create the emotion cache file. To do that, create a new
createEmotionCache.ts
file in the app
folder.
// createEmotionCache.tsimport createCache from '@emotion/cache'export default function createEmotionCache() {return createCache({ key: 'css' })}
After creating the emotion cache, we need to modify the entry files for both the client and the server. We'll use our createEmotionCache function here.
// entry.client.tsximport React, { useState } from 'react'import { hydrate } from 'react-dom'import { CacheProvider } from '@emotion/react'import { RemixBrowser } from '@remix-run/react'import { ClientStyleContext } from './context'import createEmotionCache from './createEmotionCache'interface ClientCacheProviderProps {children: React.ReactNode;}function ClientCacheProvider({ children }: ClientCacheProviderProps) {const [cache, setCache] = useState(createEmotionCache())function reset() {setCache(createEmotionCache())}return (<ClientStyleContext.Provider value={{ reset }}><CacheProvider value={cache}>{children}</CacheProvider></ClientStyleContext.Provider>)}hydrate(<ClientCacheProvider><RemixBrowser /></ClientCacheProvider>,document)
// entry.server.tsximport { renderToString } from 'react-dom/server'import { CacheProvider } from '@emotion/react'import createEmotionServer from '@emotion/server/create-instance'import { RemixServer } from '@remix-run/react'import type { EntryContext } from '@remix-run/node' // Depends on the runtime you chooseimport { ServerStyleContext } from './context'import createEmotionCache from './createEmotionCache'export default function handleRequest(request: Request,responseStatusCode: number,responseHeaders: Headers,remixContext: EntryContext) {const cache = createEmotionCache()const { extractCriticalToChunks } = createEmotionServer(cache)const html = renderToString(<ServerStyleContext.Provider value={null}><CacheProvider value={cache}><RemixServer context={remixContext} url={request.url} /></CacheProvider></ServerStyleContext.Provider>)const chunks = extractCriticalToChunks(html)const markup = renderToString(<ServerStyleContext.Provider value={chunks.styles}><CacheProvider value={cache}><RemixServer context={remixContext} url={request.url} /></CacheProvider></ServerStyleContext.Provider>)responseHeaders.set('Content-Type', 'text/html')return new Response(`<!DOCTYPE html>${markup}`, {status: responseStatusCode,headers: responseHeaders,})}
Inside our root.tsx
file we'll create a Document
wrapper and then we'll wrap
our App
with the Document.
// root.tsximport React, { useContext, useEffect } from 'react'import { withEmotionCache } from '@emotion/react'import { SaasProvider } from '@saas-ui/react'import {Links,LiveReload,Meta,Outlet,Scripts,ScrollRestoration,} from '@remix-run/react'import { MetaFunction, LinksFunction } from '@remix-run/node' // Depends on the runtime you chooseimport { ServerStyleContext, ClientStyleContext } from './context'export const meta: MetaFunction = () => ({charset: 'utf-8',title: 'New Remix App',viewport: 'width=device-width,initial-scale=1',});export let links: LinksFunction = () => {return [{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },{ rel: 'preconnect', href: 'https://fonts.gstatic.com' },{rel: 'stylesheet',href: 'https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap'},]}interface DocumentProps {children: React.ReactNode;}const Document = withEmotionCache(({ children }: DocumentProps, emotionCache) => {const serverStyleData = useContext(ServerStyleContext);const clientStyleData = useContext(ClientStyleContext);// Only executed on clientuseEffect(() => {// re-link sheet containeremotionCache.sheet.container = document.head;// re-inject tagsconst tags = emotionCache.sheet.tags;emotionCache.sheet.flush();tags.forEach((tag) => {(emotionCache.sheet as any)._insertTag(tag);});// reset cache to reapply global stylesclientStyleData?.reset();}, []);return (<html lang="en"><head><Meta /><Links />{serverStyleData?.map(({ key, ids, css }) => (<stylekey={key}data-emotion={`${key} ${ids.join(' ')}`}dangerouslySetInnerHTML={{ __html: css }}/>))}</head><body>{children}<ScrollRestoration /><Scripts /><LiveReload /></body></html>);});
And then we'll wrap the App just like so:
export default function App() {return (<Document><SaasProvider><Outlet /></SaasProvider></Document>)}
3. Customizing Theme#
If you intend to customise the default theme object to match your design
requirements, you can extend the theme
from @chakra-ui/react
.
Chakra UI provides an extendTheme
function that deep merges the default theme
with your customizations.
// 1. Import the extendTheme functionimport { extendTheme } from '@chakra-ui/react'// 2. Import the Saas UI themeimport { SaasProvider, theme } from '@saas-ui/react'// 2. Extend the theme to include custom colors, fonts, etcconst colors = {brand: {900: '#1a365d',800: '#153e75',700: '#2a69ac',},}const theme = extendTheme({ colors }, theme)// 3. Pass the `theme` prop to the `SaasProvider`export default function App() {return (<Document><SaasProvider theme={theme}><Outlet /></SaasProvider></Document>)}
Notes on TypeScript 🚨#
Please note that when adding Chakra UI to a TypeScript project, a minimum
TypeScript version of 4.1.0
is required.
Was this helpful?