Skip to content

Commit

Permalink
Merge branch 'dev' into release-next
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 committed Oct 11, 2024
2 parents 51fb9a6 + 8f44d01 commit d0ab074
Show file tree
Hide file tree
Showing 58 changed files with 1,157 additions and 472 deletions.
5 changes: 5 additions & 0 deletions .changeset/little-cooks-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router": patch
---

Fix typegen for routes with a client loader but no server loader
8 changes: 8 additions & 0 deletions .changeset/rare-plums-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@react-router/serve": patch
"@react-router/dev": patch
"react-router": patch
---

- Fix `react-router-serve` handling of prerendered HTML files by removing the `redirect: false` option so it now falls back on the default `redirect: true` behavior of redirecting from `/folder` -> `/folder/` which will then pick up `/folder/index.html` from disk. See https://expressjs.com/en/resources/middleware/serve-static.html
- Proxy prerendered loader data into prerender pass for HTML files to avoid double-invocations of the loader at build time
5 changes: 5 additions & 0 deletions .changeset/three-seals-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-router/dev": minor
---

Add `prefix` route config helper to `@react-router/dev/routes`
2 changes: 2 additions & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- andreiduca
- antonmontrezor
- appden
- apple-yagi
- arjunyel
- arka1002
- Armanio
Expand Down Expand Up @@ -207,6 +208,7 @@
- OlegDev1
- omahs
- omar-moquete
- OnurGvnc
- p13i
- parched
- parveen232
Expand Down
16 changes: 2 additions & 14 deletions docs/misc/pre-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ If you pre-render all of the paths in your application, you can deploy your `bui

### Serving via react-router-serve

By default, `react-router-serve` will serve these files via [`express.static`][express-static] and any paths that do not match a static file will fall through to the Remix handler.
By default, `react-router-serve` will serve these files via [`express.static`][express-static] and any paths that do not match a static file will fall through to the React Router handler.

This even allows you to run a hybrid setup where _some_ of your routes are pre-rendered and others are dynamically rendered at runtime. For example, you could prerender anything inside `/blog/*` and server-render anything inside `/auth/*`.

Expand All @@ -111,19 +111,7 @@ app.use(
);

// Serve static HTML and .data requests without Cache-Control
app.use(
"/",
express.static("build/client", {
// Don't redirect directory index.html requests to include a trailing slash
redirect: false,
setHeaders: function (res, path) {
// Add the proper Content-Type for turbo-stream data responses
if (path.endsWith(".data")) {
res.set("Content-Type", "text/x-turbo");
}
},
})
);
app.use("/", express.static("build/client"));

// Serve remaining unhandled requests via your React Router handler
app.all(
Expand Down
58 changes: 44 additions & 14 deletions docs/start/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ Routes are configured in `app/routes.ts`. Routes have a url pattern to match the
import { route } from "@react-router/dev/routes";

export const routes = [
route("some/path", "./some/file.tsx");
route("some/path", "./some/file.tsx"),
// pattern ^ ^ module file
]
];
```

Here is a larger sample route config:
Expand All @@ -37,6 +37,7 @@ import {
route,
index,
layout,
prefix,
} from "@react-router/dev/routes";

export const routes: RouteConfig = [
Expand All @@ -48,7 +49,7 @@ export const routes: RouteConfig = [
route("register", "./auth/register.tsx"),
]),

route("concerts", [
...prefix("concerts", [
index("./concerts/home.tsx"),
route(":city", "./concerts/city.tsx"),
route("trending", "./concerts/trending.tsx"),
Expand All @@ -63,7 +64,7 @@ If you prefer to define your routes via file naming conventions rather than conf
The files referenced in `routes.ts` define each route's behavior:

```tsx filename=app/routes.ts
route("teams/:teamId", "./team.tsx");
route("teams/:teamId", "./team.tsx"),
// route module ^^^^^^^^
```

Expand All @@ -83,7 +84,7 @@ export async function loader({ params }: Route.LoaderArgs) {
export default function Component({
loaderData,
}: Route.ComponentProps) {
return <h1>{data.name}</h1>;
return <h1>{loaderData.name}</h1>;
}
```

Expand Down Expand Up @@ -136,20 +137,21 @@ Every route in `routes.ts` is nested inside the special `app/root.tsx` module.

Using `layout`, layout routes create new nesting for their children, but they don't add any segments to the URL. It's like the root route but they can be added at any level.

```tsx filename=app/routes.ts lines=[9,15]
```tsx filename=app/routes.ts lines=[10,16]
import {
type RouteConfig,
route,
layout,
index,
prefix,
} from "@react-router/dev/routes";

export const routes: RouteConfig = [
layout("./marketing/layout.tsx", [
index("./marketing/home.tsx"),
route("contact", "./marketing/contact.tsx"),
]),
route("projects", [
...prefix("projects", [
index("./projects/home.tsx"),
layout("./projects/project-layout.tsx", [
route(":pid", "./projects/project.tsx"),
Expand All @@ -162,7 +164,7 @@ export const routes: RouteConfig = [
## Index Routes

```ts
index(componentFile);
index(componentFile),
```

Index routes render into their parent's [Outlet][outlet] at their parent's URL (like a default child route).
Expand All @@ -187,19 +189,47 @@ export const routes: RouteConfig = [

Note that index routes can't have children.

## Route Prefixes

Using `prefix`, you can add a path prefix to a set of routes without needing to introduce a parent route file.

```tsx filename=app/routes.ts lines=[14]
import {
type RouteConfig,
route,
layout,
index,
prefix,
} from "@react-router/dev/routes";

export const routes: RouteConfig = [
layout("./marketing/layout.tsx", [
index("./marketing/home.tsx"),
route("contact", "./marketing/contact.tsx"),
]),
...prefix("projects", [
index("./projects/home.tsx"),
layout("./projects/project-layout.tsx", [
route(":pid", "./projects/project.tsx"),
route(":pid/edit", "./projects/edit-project.tsx"),
]),
]),
];
```

## Dynamic Segments

If a path segment starts with `:` then it becomes a "dynamic segment". When the route matches the URL, the dynamic segment will be parsed from the URL and provided as `params` to other router APIs.

```ts filename=app/routes.ts
route("teams/:teamId", "./team.tsx");
route("teams/:teamId", "./team.tsx"),
```

```tsx filename=app/team.tsx
import type * as Route from "./+types.team";

async function loader({ params }: Route.LoaderArgs) {
// ^? { teamId: string }
export async function loader({ params }: Route.LoaderArgs) {
// ^? { teamId: string }
}

export default function Component({
Expand All @@ -213,7 +243,7 @@ export default function Component({
You can have multiple dynamic segments in one route path:

```ts filename=app/routes.ts
route("c/:categoryId/p/:productId", "./product.tsx");
route("c/:categoryId/p/:productId", "./product.tsx"),
```

```tsx filename=app/product.tsx
Expand All @@ -229,7 +259,7 @@ async function loader({ params }: LoaderArgs) {
You can make a route segment optional by adding a `?` to the end of the segment.

```ts filename=app/routes.ts
route(":lang?/categories", "./categories.tsx");
route(":lang?/categories", "./categories.tsx"),
```

You can have optional static segments, too:
Expand All @@ -243,7 +273,7 @@ route("users/:userId/edit?", "./user.tsx");
Also known as "catchall" and "star" segments. If a route path pattern ends with `/*` then it will match any characters following the `/`, including other `/` characters.

```ts filename=app/routes.ts
route("files/*", "./files.tsx");
route("files/*", "./files.tsx"),
```

```tsx filename=app/files.tsx
Expand Down
123 changes: 113 additions & 10 deletions docs/upgrading/remix.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,125 @@ hidden: true

# Upgrading from Remix

<docs-warning>This guide is still in development</docs-warning>
<docs-warning>This guide is still in development and is subject to change as React Router stabilizes prior to the `7.0.0` stable release</docs-warning>

After the final React Router v7 release, we will go back to Remix to add future flags to any changed APIs.
Our intention is for the **Remix v2 -> React Router v7** upgrade path to be as non-breaking as possible via the use of [Future Flags][future-flags] and codemods for minor and straightforward code adjustments. To best prepare for this eventual upgrade, you can start by adopting all of the existing [Remix v2 Future Flags][v2-future-flags].

If you want to attempt the rocky migration now, the following table will be helpful:
## Upgrading to the v7 Prerelease

| Remix v2 Package | | React Router v7 Package |
| ----------------------- | --- | -------------------------- |
| `@remix-run/react` | ➡️ | `react-router` |
| `@remix-run/dev` | ➡️ | `@react-router/dev` |
| `@remix-run/node` | ➡️ | `@react-router/node` |
| `@remix-run/cloudflare` | ➡️ | `@react-router/cloudflare` |
If you want to attempt the (potentially rocky) migration now, the following steps should get you most of the way there. If you run into issues please let us know in [Discord][remix-discord] or [Github][github-new-issue].

Also note that nearly all modules your app needs come from `react-router` now instead of `@remix-run/node` and `@remix-run/cloudflare`, so try to import from there first.
### Step 1 - Adopt future flags

Adopt all existing [future flags][v2-future-flags] in your Remix v2 application.

### Step 2 - Update dependencies

You'll need to update your dependencies from the `@remix-run/*` packages to `react-router` and `@react-router/*` packages in `package.json` and in your code where you import from packages:

| Remix v2 Package | | React Router v7 Package |
| --------------------------- | --- | -------------------------- |
| `@remix-run/architect` | ➡️ | `@react-router/architect` |
| `@remix-run/cloudflare` | ➡️ | `@react-router/cloudflare` |
| `@remix-run/dev` | ➡️ | `@react-router/dev` |
| `@remix-run/express` | ➡️ | `@react-router/express` |
| `@remix-run/node` | ➡️ | `@react-router/node` |
| `@remix-run/react` | ➡️ | `react-router` |
| `@remix-run/serve` | ➡️ | `@react-router/serve` |
| `@remix-run/server-runtime` | ➡️ | `react-router` |
| `@remix-run/testing` | ➡️ | `react-router` |

Most of the "shared" APIs that used to be re-exported through the runtime-specific packages (`@remix-run/node`, `@remix-run/cloudflare`, etc.) have all been collapsed into `react-router` in v7. So instead of importing from `@react-router/node` or `@react-router/cloudflare`, you'll import those directly from `react-router`.

```diff
-import { redirect } from "@react-router/node";
+import { redirect } from "react-router";
```

The only APIs should be importing from the runtime-specific packages in v7 are APIs that are specific to that runtime, such as `createFileSessionStorage` for Node and `createWorkersKVSessionStorage` for Cloudflare.

### Step 3 - Change `scripts` in `package.json`

Update the scripts in your `package.json`:

| Script | Remix v2 | | React Router v7 |
| ----------- | ----------------------------------- | --- | ------------------------------------------ |
| `dev` | `remix vite:dev` | ➡️ | `react-router dev` |
| `build` | `remix vite:build` | ➡️ | `react-router build` |
| `start` | `remix-serve build/server/index.js` | ➡️ | `react-router-serve build/server/index.js` |
| `typecheck` | `tsc` | ➡️ | `react-router typegen && tsc` |

### Step 4 - Rename plugin in `vite.config`

Update the import and rename the plugin in your `vite.config.ts`:

```diff
-import { vitePlugin as remix } from "@remix-run/dev";
+import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
plugins: [
- remix({
- future: {
- // all future flags adopted
- },
- }),
+ reactRouter(),
tsconfigPaths(),
],
});
```

### Step 5 - Add a `routes.ts` file

In React Router v7 you define your routes using the [`app/routes.ts`][routing] file. For backwards-compatibility and for folks who prefer [file-based conventions][fs-routing], you can opt-into the same "flat routes" convention you are using in Remix v2 via the new `@react-router/fs-routes` package:

```ts filename=app/routes.ts
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export const routes: RouteConfig = flatRoutes();
```

### Step 6 - Rename components in entry files

If you have an `entry.server.tsx` and/or an `entry.client.tsx` file in your application, you will need to rename the main components in this files:

| Entry File | Remix v2 Component | | React Router v7 Component |
| ------------------ | ------------------ | --- | ------------------------- |
| `entry.server.tsx` | `<RemixServer>` | ➡️ | `<ServerRouter>` |
| `entry.client.stx` | `<RemixBrowser>` | ➡️ | `<HydratedRouter>` |

## Known Prerelease Issues

### Typesafety

We have introduced some major changes to improve the type story in v7, but we're still working on making sure the path to adopt them is as smooth as possible prior to a stable v7 release. You can read more about the new type story in the [v7 draft release notes][v7-changelog-types] and if it's not a huge lift, your best bet for types in v7 is to migrate to that approach across the board.

For the time being we don't have a great story to _incrementally_ migrate data types to the v7 prerelease. We never brought the generics on data APIs (`useLoaderData`, `useFetcher`, `Await`, etc.) over from Remix to React Router because we knew that we could do better than what Remix v2 had, and we wanted to ensure that we didn't ship APIs in React Router just to yank them out. Now that we have a better idea of the type story in React Router v7, we're better able to see what the migration path looks like and we plan on shipping improvements in this area in an upcoming v7 prerelease.

Currently, when you upgrade to React Router v7 you're going to get typescript yelling at you a bunch for these missing generics that existed in your Remix v2 app code. For now, you have 2 options to continue testing out the prerelease:

**Option 1 - Ignore the type errors with `@ts-expect-error` or `@ts-ignore`**

```diff
+// @ts-expect-error
let data = useLoaderData<typeof loader>();
```

**Option 2 - Remove the generics and cast the types manually**

```diff
-let data = useLoaderData<typeof loader>();
+let data = useLoaderData() as ReturnType<Awaited<typeof loader>>;
```

[future-flags]: ../community/api-development-strategy
[v2-future-flags]: https://remix.run/docs/start/future-flags
[remix-discord]: https://rmx.as/discord
[github-new-issue]: https://github.com/remix-run/react-router/issues/new/choose
[routing]: ../start/routing
[fs-routing]: ../misc/file-route-conventions
[v7-changelog-types]: https://github.com/remix-run/react-router/blob/release-next/CHANGELOG.md#typesafety-improvements
2 changes: 1 addition & 1 deletion docs/upgrading/vite-router-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ The first few routes you migrate are the hardest because you often have to acces

## Enable SSR and/or Pre-rendering

If you want to enable server rendering and static pre-rendering, you can do so with the `ssr` and `prerender` options in the bundler plugin. For SSR you'll need to also deploy the server build to a server. See [Deploying](./deploying) for more information.
If you want to enable server rendering and static pre-rendering, you can do so with the `ssr` and `prerender` options in the bundler plugin. For SSR you'll need to also deploy the server build to a server. See [Deploying](../start/deploying) for more information.

```ts filename=vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
Expand Down
Loading

0 comments on commit d0ab074

Please sign in to comment.