react-router
React Router v7 (Data Mode for Vite SPA + Convex)
Use Data Mode (createBrowserRouter + RouterProvider) for Vite SPAs with Convex.
Attribution: remix-run/react-router
Key Principle
Convex handles data, Router handles navigation.
- React Router: routing, navigation, URL params, nested layouts, lazy loading
- Convex: data fetching (
useQuery), mutations (useMutation), real-time updates
Don't use React Router loaders/actions with Convex.
Installation
react-router-dom
(Install with your project's package manager)
Basic Setup
// main.tsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { ConvexProvider, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
const router = createBrowserRouter([
{
path: "/",
Component: Layout,
ErrorBoundary: RootError,
children: [
{ index: true, Component: Home },
{ path: "projects", Component: Projects },
{ path: "projects/:projectId", Component: Project },
],
},
]);
// HMR cleanup for Vite
if (import.meta.hot) {
import.meta.hot.dispose(() => router.dispose());
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<ConvexProvider client={convex}>
<RouterProvider router={router} fallbackElement={<p>Loading...</p>} />
</ConvexProvider>
</StrictMode>
);
Nested Routes & Outlet
function Layout() {
return (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/projects">Projects</Link>
</nav>
<Outlet /> {/* Child routes render here */}
</div>
);
}
URL Params + Convex Query
function Project() {
const { projectId } = useParams<{ projectId: string }>();
const project = useQuery(
api.projects.get,
projectId ? { id: projectId as Id<"projects"> } : "skip"
);
if (project === undefined) return <p>Loading...</p>;
if (project === null) return <p>Not found</p>;
return <h1>{project.name}</h1>;
}
Mutation + Navigate
function CreateProject() {
const navigate = useNavigate();
const createProject = useMutation(api.projects.create);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const id = await createProject({ name: formData.get("name") as string });
navigate(`/projects/${id}`);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" required />
<button type="submit">Create</button>
</form>
);
}
Protected Routes
function ProtectedRoute() {
const { isAuthenticated, isLoading } = useConvexAuth();
const location = useLocation();
if (isLoading) return <p>Loading...</p>;
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <Outlet />;
}
// Usage in router
{
Component: ProtectedRoute,
children: [
{ path: "dashboard", Component: Dashboard },
],
}
Quick Reference
| Task | API |
|---|---|
| Navigate programmatically | useNavigate() |
| Get URL params | useParams() |
| Get/set search params | useSearchParams() |
| Get current location | useLocation() |
| Check navigation state | useNavigation() |
| Link with active state | <NavLink> |
| Render child routes | <Outlet /> |
| Code split routes | lazy: () => import() |
Critical Rules
- Use Convex hooks for data -
useQuery/useMutationprovide real-time updates - Don't use loaders/actions - Convex handles data, Router handles navigation
- Handle loading states -
useQueryreturnsundefinedwhile loading - Dispose router in HMR - Prevents memory leaks in Vite dev mode
- Wrap Router in ConvexProvider - Convex context must be available
Deep Dive References
- examples.md - Complete CRUD, auth patterns, search params
- api.md - All hooks and components reference
More from sebastiaanwouters/dotagents
flyctl
Deploy and manage apps on Fly.io using flyctl CLI. Triggers on: fly deploy, fly.io, flyctl, deploy to fly. Handles launch, deploy, scale, secrets, volumes, databases.
79teacher
Guide learning and deep understanding through proven methodologies (Socratic, Feynman, Problem-Based). Use when user says "help me understand", "teach me", "explain this", "learn about", "socratic", "feynman", "problem-based", "I don't understand", "confused about", "why does", or wants to truly grasp a concept.
77chef
Telegram communication for AI agents. ALL methods are BLOCKING. Use for user interviews, status updates, and feedback collection.
34bitwarden
Retrieves API keys, passwords, secrets from Bitwarden vault using bw CLI. Triggers on missing env variables, missing API keys, missing secrets, "secret not found", "env not set", or "use bw".
29librarian
Use for code research that needs dependency internals, upstream implementation examples, or external prior art. Always delegate to a subagent that investigates with opensrc and web search, then return only distilled findings, versions, paths, and links.
29frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications. Generates creative, polished code that avoids generic AI aesthetics.
28