Table of Contents:
Import any CSS styles
If you’re using Remix with Vite, you can import styles like this:
import type { LinksFunction } from "@remix-run/node";
import stylesheet from "@/styles/globals.css?url"; // 👈
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
];
and that the Vite compiler does not interpret .css
files as paths:
export default defineConfig({
plugins: [
remix({
ignoredRouteFiles: ["**/*.css"], // 👈
}),
],
});
Routes
A guide for managing routes in Remix v2:
📂 app
📂 routes
| - ⚛️ app.tsx -> layout that wraps all the paths of /app. You have to add <Outlet /> from @remix-run/react.
| - 🟦 action.set-theme.ts -> /action/set-theme. Exports a server-side ``action``. Ideal for use with <Form /> from @remix-run/react.
| - ⚛️ app._index.tsx -> /app (main page)
| - ⚛️ app.settings.tsx -> /app/settings
| - ⚛️ app.$username.tsx -> /app/pheralb o /app/midudev. 💡
| - ⚛️ root.tsx -> layout that wraps the entire application.
- 💡 To obtain the $username: remix.run/docs/en/main/route/loader#params
How to use Remix on Vercel
- Install
@vercel/remix
package:
pnpm i @vercel/remix -E
- Add Vercel Vite Preset to your
vite.config.ts
:
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
// Plugins:
import tsconfigPaths from "vite-tsconfig-paths";
import { vercelPreset } from "@vercel/remix/vite"; // 👈
export default defineConfig({
plugins: [
remix({
presets: [vercelPreset()], // 👈
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
},
}),
tsconfigPaths(),
],
});
- Replace all
@remix-run/node
imports with@vercel/remix
. Example:
import type { MetaFunction } from "@vercel/remix";
export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{ name: "description", content: "Welcome to Remix!" },
];
};
- 💡 Check
@vercel/remix
documentation.
Install & configure Tailwind CSS with Prettier
- Install Tailwind CSS, Prettier & Autoprefixer:
pnpm i tailwindcss prettier prettier-plugin-tailwindcss autoprefixer -E -D
- Create a
global.css
orstyles.css
in theapp/
folder:
@tailwind base;
@tailwind components;
@tailwind utilities;
- Import the styles in the
app/routes/root.tsx
:
import type { LinksFunction } from "@remix-run/node";
import stylesheet from "@/styles/globals.css?url"; // or styles.css 👀
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
];
- Create a
tailwind.config.ts
file:
npx tailwindcss init --ts
- In the
tailwind.config.ts
file, add the following:
export default {
content: ['./app/**/*.{js,jsx,ts,tsx}'], // 👈
...
} satisfies Config
- Create a
postcss.config.js
file:
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
- Create a
prettier.config.js
file:
module.exports = {
plugins: ['prettier-plugin-tailwindcss'],
...
};
Dark mode with Tailwind CSS
- Install remix-themes and clsx:
pnpm i remix-themes clsx -E
- Enable
darkMode
option in thetailwind.config.ts
file:
const config = {
darkMode: ['class'],
//...
} satisfies Config;
- Create a
session.server.tsx
file in /app folder with the following content:
import { createCookieSessionStorage } from '@remix-run/node'; //
import { createThemeSessionResolver } from 'remix-themes';
// You can default to 'development' if process.env.NODE_ENV is not set:
const isProduction = process.env.NODE_ENV === 'production';
const sessionStorage = createCookieSessionStorage({
cookie: {
name: 'website-theme', // 👈 Cookie name.
path: '/',
httpOnly: true,
sameSite: 'lax',
secrets: ['s3cr3t'],
...(isProduction ? { domain: 'my-website.com', secure: true } : {}), // 👈 Website URL.
},
});
export const themeSessionResolver = createThemeSessionResolver(sessionStorage);
- In
app/routes
folder, create aaction.set-theme.ts
file with the following content:
import { createThemeAction } from 'remix-themes';
import { themeSessionResolver } from '@/sessions.server'; // 👈 Import your session.server.tsx.
export const action = createThemeAction(themeSessionResolver);
- In
app/routes/root.tsx
…
- ☁️ Use the loader to get the theme from server-side:
import type { LoaderFunctionArgs } from '@remix/node';
import { themeSessionResolver } from './sessions.server';
export async function loader({ request }: LoaderFunctionArgs) {
const { getTheme } = await themeSessionResolver(request);
return {
theme: getTheme(),
};
}
- 📦 Wrap the entire app using
ThemeProvider
:
import { ThemeProvider } from 'remix-themes';
import { useLoaderData } from '@remix-run/react';
export default function AppWithProviders() {
const data = useLoaderData<typeof loader>();
return (
<ThemeProvider specifiedTheme={data.theme} themeAction="/action/set-theme">
<App />
</ThemeProvider>
);
}
function App() {
return (
<html lang="en">
<!-- ... -->
</html>
);
}
- ✨ In your
<App />
component, use theuseTheme
hook:
import { useLoaderData } from '@remix-run/react';
import { PreventFlashOnWrongTheme, useTheme } from 'remix-themes';
import clsx from 'clsx';
function App() {
const data = useLoaderData<typeof loader>();
const [theme] = useTheme();
return (
<html lang="en" className={clsx(theme)}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<PreventFlashOnWrongTheme ssrTheme={Boolean(data.theme)} />
<Links />
</head>
<body className="bg-white text-black dark:bg-black dark:text-white">
<Outlet />
<Scripts />
</body>
</html>
);
}
- Create a simple
theme-toggle.tsx
component:
import { Moon, Sun } from 'lucide-react'; // 🥹 Icons from lucide.dev
import { Theme, useTheme } from 'remix-themes';
export function ModeToggle() {
const [theme, setTheme] = useTheme();
return (
<>
<button onClick={() => setTheme(Theme.LIGHT)}>
<Sun size={22} strokeWidth={1.4} />
<span>Light Theme</span>
</button>
<button onClick={() => setTheme(Theme.DARK)}>
<Moon size={22} strokeWidth={1.4} />
<span>Dark Theme</span>
</button>
</>
);
}