From c31fff722aa6d53112906fe623cfd431c78916e6 Mon Sep 17 00:00:00 2001 From: Alexandre Paixao Date: Thu, 2 Oct 2025 16:24:55 +0200 Subject: [PATCH 1/5] Show typegen behavior for routes outside appDirectory --- integration/typegen-test.ts | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index 01664a296a..0d5304c5e1 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -1,5 +1,6 @@ import fs from "node:fs/promises"; +import { expect } from "@playwright/test"; import tsx from "dedent"; import * as Path from "pathe"; @@ -336,6 +337,57 @@ test.describe("typegen", () => { await $("pnpm typecheck"); }); + test("routes outside app dir", async ({ cwd, edit, $ }) => { + // Create the subdirectories + await fs.mkdir(Path.join(cwd, "app/router"), { recursive: true }); + await fs.mkdir(Path.join(cwd, "app/pages"), { recursive: true }); + + await edit({ + "react-router.config.ts": tsx` + export default { + appDirectory: "app/router", + } + `, + "app/router/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("products/:id", "../pages/product.tsx") + ] satisfies RouteConfig; + `, + "app/router/root.tsx": tsx` + import { Outlet } from "react-router"; + + export default function Root() { + return ; + } + `, + "app/pages/product.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/product" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { planet: "world" } + } + + export default function Component({ loaderData }: Route.ComponentProps) { + type Test = Expect> + return

Hello, {loaderData.planet}!

+ } + `, + }); + await $("pnpm typecheck"); + + // Verify that the types file was generated in the correct location + const annotationPath = Path.join( + cwd, + ".react-router/types/app/pages/+types/product.ts", + ); + const annotation = await fs.readFile(annotationPath, "utf8"); + expect(annotation).toContain("export namespace Route"); + }); + test("matches", async ({ edit, $ }) => { await edit({ "app/routes.ts": tsx` From 369ce68afb5f9cca340fb3da5f7f18985c6fcd17 Mon Sep 17 00:00:00 2001 From: Alexandre Paixao Date: Thu, 2 Oct 2025 16:24:55 +0200 Subject: [PATCH 2/5] Add typegen support for routes outside appDirectory --- .changeset/yellow-carpets-own.md | 5 +++++ contributors.yml | 1 + packages/react-router-dev/typegen/generate.ts | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 .changeset/yellow-carpets-own.md diff --git a/.changeset/yellow-carpets-own.md b/.changeset/yellow-carpets-own.md new file mode 100644 index 0000000000..1a212de4f5 --- /dev/null +++ b/.changeset/yellow-carpets-own.md @@ -0,0 +1,5 @@ +--- +"@react-router/dev": patch +--- + +Fix typegen support for routes outside appDirectory diff --git a/contributors.yml b/contributors.yml index 72a1f6fcbf..9c624b2e78 100644 --- a/contributors.yml +++ b/contributors.yml @@ -20,6 +20,7 @@ - alberto - AlemTuzlak - Aleuck +- alex-pex - alexandernanberg - alexanderson1993 - alexlbr diff --git a/packages/react-router-dev/typegen/generate.ts b/packages/react-router-dev/typegen/generate.ts index 72b5b46fb1..9019b7fd2f 100644 --- a/packages/react-router-dev/typegen/generate.ts +++ b/packages/react-router-dev/typegen/generate.ts @@ -119,7 +119,7 @@ export function generateRoutes(ctx: Context): Array { // **/+types/*.ts const allAnnotations: Array = Array.from(fileToRoutes.entries()) - .filter(([file]) => isInAppDirectory(ctx, file)) + .filter(([file]) => isInRootDirectory(ctx, file)) .map(([file, routeIds]) => getRouteAnnotations({ ctx, file, routeIds, lineages }), ); @@ -221,9 +221,9 @@ function routeModulesType(ctx: Context) { ); } -function isInAppDirectory(ctx: Context, routeFile: string): boolean { +function isInRootDirectory(ctx: Context, routeFile: string): boolean { const path = Path.resolve(ctx.config.appDirectory, routeFile); - return path.startsWith(ctx.config.appDirectory); + return path.startsWith(ctx.rootDirectory); } function getRouteAnnotations({ From f52d8af31c50a4342702e772102223a15f94bcac Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 13 Nov 2025 10:36:31 -0500 Subject: [PATCH 3/5] tests: automatically create directories for `edit`s --- integration/helpers/fixtures.ts | 1 + integration/typegen-test.ts | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/integration/helpers/fixtures.ts b/integration/helpers/fixtures.ts index ab644568ae..68c0878bbf 100644 --- a/integration/helpers/fixtures.ts +++ b/integration/helpers/fixtures.ts @@ -31,6 +31,7 @@ type Edits = Record string)>; async function applyEdits(cwd: string, edits: Edits) { const promises = Object.entries(edits).map(async ([file, transform]) => { const filepath = Path.join(cwd, file); + await fs.mkdir(Path.dirname(filepath), { recursive: true }); await fs.writeFile( filepath, typeof transform === "function" diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index 0d5304c5e1..b461920663 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -338,10 +338,6 @@ test.describe("typegen", () => { }); test("routes outside app dir", async ({ cwd, edit, $ }) => { - // Create the subdirectories - await fs.mkdir(Path.join(cwd, "app/router"), { recursive: true }); - await fs.mkdir(Path.join(cwd, "app/pages"), { recursive: true }); - await edit({ "react-router.config.ts": tsx` export default { From 0d0fa513faa8b2972bfd5899d31ec463851e869f Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 13 Nov 2025 10:41:54 -0500 Subject: [PATCH 4/5] remove checks for exact typegen file locations since this is an implementation detail --- integration/typegen-test.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index b461920663..57997c9ad9 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -1,6 +1,5 @@ import fs from "node:fs/promises"; -import { expect } from "@playwright/test"; import tsx from "dedent"; import * as Path from "pathe"; @@ -337,7 +336,7 @@ test.describe("typegen", () => { await $("pnpm typecheck"); }); - test("routes outside app dir", async ({ cwd, edit, $ }) => { + test("routes outside app dir", async ({ edit, $ }) => { await edit({ "react-router.config.ts": tsx` export default { @@ -374,14 +373,6 @@ test.describe("typegen", () => { `, }); await $("pnpm typecheck"); - - // Verify that the types file was generated in the correct location - const annotationPath = Path.join( - cwd, - ".react-router/types/app/pages/+types/product.ts", - ); - const annotation = await fs.readFile(annotationPath, "utf8"); - expect(annotation).toContain("export namespace Route"); }); test("matches", async ({ edit, $ }) => { From deb5de0f882cdecf1b2647fc190846e7a8bea189 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 13 Nov 2025 10:44:37 -0500 Subject: [PATCH 5/5] changeset --- .changeset/yellow-carpets-own.md | 2 +- integration/typegen-test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/yellow-carpets-own.md b/.changeset/yellow-carpets-own.md index 1a212de4f5..999a1e3df0 100644 --- a/.changeset/yellow-carpets-own.md +++ b/.changeset/yellow-carpets-own.md @@ -2,4 +2,4 @@ "@react-router/dev": patch --- -Fix typegen support for routes outside appDirectory +Include all routes within the React Router root directory when performing typegen, not just routes within the app directory. diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index 57997c9ad9..4124668025 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -336,7 +336,7 @@ test.describe("typegen", () => { await $("pnpm typecheck"); }); - test("routes outside app dir", async ({ edit, $ }) => { + test("routes within root dir, but outside app dir", async ({ edit, $ }) => { await edit({ "react-router.config.ts": tsx` export default {