STSX
React Router
In React Router's framework mode, components can be .stsx files: their CSS is linked per route in server-rendered HTML, in dev and production. Route modules stay .tsx files that render those components.
// vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import stsx from "@lumiose/stsx";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [stsx({ classAttribute: "className" }), reactRouter()],
});#Route modules are .tsx
React Router finds a route module by the file path in routes.ts, and strips its server-only
exports (loader, action) from the client build. It does that only for files it compiles
itself, so a route module cannot be a .stsx file. The plugin stops the build, and dev server
rendering, with an error that says so. It spots a route module in two ways: a .stsx file that
exports a route-only name (loader, action, clientLoader, clientAction, middleware,
clientMiddleware or HydrateFallback), and one that React Router imports as a route. Keep the
route in .tsx, and put the markup and its styles in a .stsx component:
// app/routes/about.tsx
import { AboutCard } from "../components/AboutCard.stsx";
import type { Route } from "./+types/about";
export function loader() {
return { message: "Hello from the loader" };
}
export default function About({ loaderData }: Route.ComponentProps) {
return <AboutCard message={loaderData.message} />;
}// app/components/AboutCard.stsx
export function AboutCard({ message }: { message: string }) {
return (
<section className="about">
<h1>About</h1>
<p className="message">{message}</p>
</section>
);
}
<style>
.about { padding: 20px; border-radius: 14px; background: #d1fae5; }
.message { font-style: italic; }
</style>root.tsx can render .stsx components too: a layout shell, a navigation bar.
#CSS per route
- Production. Each route's stylesheet holds the CSS of the
.stsxcomponents it imports. React Router links it in the route's server-rendered<head>, and client navigations load it before the route renders. - Dev. React Router's critical CSS includes the
.stsxstyles, so pages do not flash unstyled. An edit inside a<style>block hot-replaces the stylesheet alone, once per save. - Styling a
NavLinkorLink. It renders its<a>in your component's markup, but as another component's element: reach it with:deep(), as innav :deep(a.active), or use the class strategy so the scope travels with itsclassName.
#Types
React Router's route types and the .stsx shadows share rootDirs:
// tsconfig.json
{
"compilerOptions": {
"rootDirs": [".", "./.react-router/types", ".lumiose/stsx"],
},
"include": ["app", ".react-router/types", ".lumiose/stsx"],
}stsx typegen && react-router typegen && tsc