# Building with React Router v7
React Router is a multi-strategy router for React built on top of Vite. Gadget supports React Router in both framework and declarative modes for building app frontends.
## Getting started with React Router v7
### Picking a Mode
React Router can be run in framework or declarative mode. The mode you choose depends on which "top-level" router API you're using and whether you are comfortable with Remix-style frontend development.
Gadget's web templates offer both framework and declarative modes. Currently, the Shopify template only provides the Remix and React Router declarative mode.
#### Declarative
Declarative mode is also known as "client-side routing" or "Single Page Application (SPA) routing". Declarative mode enables basic routing features like matching URLs to components, navigating around the app, and providing active states with APIs like ``, `useNavigate`, and `useLocation`. This is the closest comparison to the React Router v6 API.
```tsx
import { BrowserRouter } from "react-router";
ReactDOM.createRoot(root).render(
);
```
#### Framework
Framework mode is very similar to the Remix framework, which supports features like server-side rendering (SSR), file-based routing and more.
However, the React Router framework mode introduces a new "typesafe Route Module API" feature that generates route-specific types for power type inference for URL parameters, loader data, and more.
All Gadget templates in the framework mode are configured with the file-based routing using the `@react-router/fs-routes` package to provide a greater development experiment and smoother transition between Remix and React Router.
For more information about the differences between both modes, check out the [guide from React Router](https://reactrouter.com/start/modes).
Gadget frontends are serverless and resources used are scaled to zero when not in use. This means that a frontend not in use needs to be started fresh, referred to as a cold boot. This cold boot time may negatively impact web vitals like LCP.
Contact us if you are building in SSR model and need to minimize cold boot times.
## Framework mode
Framework mode allows you to render your frontend on the server. Framework mode has multiple benefits:
* improved performance by reducing the time it takes to load the page
* better search engine optimization (SEO) because search engines can index the page content returned from the server
* data fetching with loader functions to reduce client-side requests
* form submissions with action functions, allowing for full-stack actions without client-side JavaScript
* code splitting and reduced bundle size
Some Gadget-provided tools will not be rendered server-side, including:
* React hooks provided by `@gadget-client/react`
* [Autocomponents](https://docs.gadget.dev/guides/frontend/autocomponents)
You could opt out of SSR in framework mode by setting `ssr: false` in the `react-router.config.ts` file. However, in this section, we only focus on the SSR.
### Auto-generated route module types
React Router generates route-specific types to power type inference for URL params, loader data, and more. Note that this is a TypeScript-only feature, and we have set it up for you.
To import the auto-generated types, you could write the import path like this:
| File path | Import statement |
| :--- | :--- |
| `web/routes/_index.tsx` | `import type { Route } from "./+types/_index";` |
| `web/routes/_user.todos.tsx` | `import type { Route } from "./+types/_user.todos";` |
| `web/routes/_public.blogs.$id.tsx` | `import type { Route } from "./+types/_public.blogs.$id";` |
Note that this conversation is only valid for the file-based routing we have set up for you.
Then you can use the `Route` type like this:
```tsx
import type { Route } from "./+types/_public.blogs.$id";
// The \`context\` is the same you get in Gadget actions.
// The \`params\` has the \`{ id: string; }\` type because the route has a \`$id\` parameter.
export const loader = async ({ context, params }: Route.LoaderArgs) => {
const blog = context.api.blog.findById(params.id);
return {
blog,
};
};
export default function ({ loaderData, params }: Route.ComponentProps) {
const blog = loaderData.blog;
return (
Blog {params.id}
{blog.title}
);
}
```
To learn more about how type safety works in React Router, check out the [guides from React Router](https://reactrouter.com/explanation/type-safety).
### Reading data with `loader` functions
You could use the `loader` function from Remix to fetch the data on the server-side.
Your Gadget app's [`context` object](https://docs.gadget-canary.xyz/guides/actions/code#action-context) is available in the `loader` function and is the same as the context you get when you create an action in Gadget. This allows you to interact with your Gadget backend and pass data to your frontend.
For example, you might have a `loader` function that fetches the model data like this:
```tsx
import type { Route } from "./+types/_user.todos";
export const loader = async ({ context, request }: Route.LoaderArgs) => {
// The `api` client will act as the current session by default, which only returns data for the logged in user
const todos = context.api.todo.findMany();
// Add the `actAsAdmin` if you want to return all data instead
// const todos = context.api.actAsAdmin.todo.findMany()
// return the data you want to pass to the frontend
return {
todos,
};
};
```
The `context` object also includes a `logger` for writing structured logs:
```tsx
export const loader = async ({ context }: Route.LoaderArgs) => {
context.logger.info({ userId: context.session?.id }, "fetching todos");
// ...
};
```
Then you can access the `todos` data in the frontend like this:
```tsx
export default function ({ loaderData }: Route.ComponentProps) {
const todos = loaderData.todos;
return loaderData.todos.map((todo) =>
{todo.title}
);
}
```
### Submitting forms with `action` functions
Route `action` functions handle form submissions and data mutations on the server. You can use the `action` export in a route file to process a `
` component from `react-router` submits to the current route's `action` function using a `POST` request by default. The action function receives the submitted values from `request.formData()`.
Route `action` functions run on the server and have access to the same [`context` object](https://docs.gadget-canary.xyz/guides/actions/code#action-context) as `loader` functions, including `context.api`, `context.logger`, and `context.session`.
For other ways to call actions, see the [React Router actions guide](https://reactrouter.com/start/data/actions).
## Declarative mode
Declarative mode enables basic routing features like matching URLs to components, navigating around the app, and providing active states with APIs like ``, `useNavigate`, and `useLocation`. It is useful if you want to use React Router as simply as possible or are coming from v6 and are happy with the ``.
Check out our guide on [building frontend](https://docs.gadget-canary.xyz/guides/templates#switching-between-framework-and-declarative-mode) for more details.
## Migrate from Remix to React Router
Follow these steps to migrate your Gadget frontend from Remix to React Router v7 (framework mode):
1. Replace Remix with React Router dependencies. First install the React Router packages:
```bash
yarn add react-router @react-router/fs-routes @react-router/node @react-router/serve
```
the required dev package:
```bash
yarn add -D @react-router/dev
```
Then remove the Remix packages you no longer need:
```bash
yarn remove @remix-run/react @remix-run/node @remix-run/dev
```
2. Replace imports from `@remix-run/react` with `react-router`. Also replace any other imports from `@remix-run/*` with the [corresponding `react-router` packages](https://reactrouter.com/upgrading/remix#2-update-dependencies):
```markdown
// in replace imports
- import { useLoaderData, Outlet } from "@remix-run/react";
+ import { useLoaderData, Outlet } from "react-router";
```
Importing `json` from `@remix-run/node` is not needed in React Router. This will be covered when you update your `loader` functions in step 7.
3. Replace `gadget-server/remix` with `gadget-server/react-router` in `web/root.jsx`:
```markdown
// in use gadget-server/react-router
- import { ProductionErrorBoundary, DevelopmentErrorBoundary } from "gadget-server/remix";
+ import { ProductionErrorBoundary, DevelopmentErrorBoundary } from "gadget-server/react-router";
```
4. Add a `web/routes.js` file and add the following content:
```typescript
import { flatRoutes } from "@react-router/fs-routes";
import type { RouteConfig } from "@react-router/dev/routes";
export default flatRoutes() satisfies RouteConfig;
```
5. Add a root level `react-router.config.js` file and add the following content:
```typescript
import { reactRouterConfigOptions } from "gadget-server/react-router";
import type { Config } from "@react-router/dev/config";
export default reactRouterConfigOptions satisfies Config;
```
6. Update `vite.config.mjs` to use the React Router plugin:
```typescript
import { defineConfig } from "vite";
import { gadget } from "gadget-server/vite";
import { reactRouter } from "@react-router/dev/vite";
import path from "path";
export default defineConfig({
plugins: [gadget(), reactRouter()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./web"),
},
},
});
```
7. Update your `loader` functions.
The `json()` helper used in Remix is not included with `react-router`.
This means you can remove imports of `json` from `@remix-run/node` and update your `loader` functions to return plain objects instead of using `json()`. For TypeScript apps, a `Route` type can be imported from the auto-generated types file for each route, of the format `./+types/path/to/route`
```tsx
import type { Route } from "./+types/root";
// use Route.LoaderArgs to type the arguments to the loader function
export const loader = async ({ context }: Route.LoaderArgs) => {
// return the data you want to pass to the frontend, no json() needed
return { gadgetConfig: context.gadgetConfig };
};
// use Route.ComponentProps to type useLoaderData
export default function MyPage() {
const { gadgetConfig } = useLoaderData();
// ...
}
```
Optionally, you can replace the `useLoaderData` hook and use a `loaderData` prop in your component:
```tsx
// access the loaderData prop in your component
export default function MyPage({ loaderData }: Route.ComponentProps) {
const { gadgetConfig } = loaderData;
// ...
}
```