diff --git a/packages/vinext/src/routing/app-router.ts b/packages/vinext/src/routing/app-router.ts index fce569f3..7df1afc6 100644 --- a/packages/vinext/src/routing/app-router.ts +++ b/packages/vinext/src/routing/app-router.ts @@ -240,6 +240,7 @@ function discoverSlotSubRoutes( // Find the default.tsx for the children slot at the parent directory const childrenDefault = findFile(parentPageDir, "default", matcher); + if (!childrenDefault) continue; for (const [subPath, slotPages] of subPathMap) { // Convert sub-path segments to URL pattern parts diff --git a/tests/routing.test.ts b/tests/routing.test.ts index 792f7608..82433382 100644 --- a/tests/routing.test.ts +++ b/tests/routing.test.ts @@ -476,6 +476,42 @@ describe("appRouter - route discovery", () => { }); }); + it("does not create nested @slot sub-routes without a children default fallback", async () => { + await withTempDir("vinext-app-slot-missing-children-default-", async (tmpDir) => { + const appDir = path.join(tmpDir, "app"); + await mkdir(path.join(appDir, "inbox", "@modal", "profile"), { recursive: true }); + await writeFile(path.join(appDir, "inbox", "page.tsx"), EMPTY_PAGE); + await writeFile(path.join(appDir, "inbox", "@modal", "default.tsx"), EMPTY_PAGE); + await writeFile(path.join(appDir, "inbox", "@modal", "profile", "page.tsx"), EMPTY_PAGE); + + invalidateAppRouteCache(); + const routes = await appRouter(appDir); + const patterns = routes.map((route) => route.pattern); + + expect(patterns).toContain("/inbox"); + expect(patterns).not.toContain("/inbox/profile"); + expect(matchAppRoute("/inbox/profile", routes)).toBeNull(); + }); + }); + + it("does not discover nested @slot sub-routes when the slot root has no page or default", async () => { + await withTempDir("vinext-app-slot-nested-only-rootless-", async (tmpDir) => { + const appDir = path.join(tmpDir, "app"); + await mkdir(path.join(appDir, "inbox", "@modal", "profile"), { recursive: true }); + await writeFile(path.join(appDir, "inbox", "page.tsx"), EMPTY_PAGE); + await writeFile(path.join(appDir, "inbox", "default.tsx"), EMPTY_PAGE); + await writeFile(path.join(appDir, "inbox", "@modal", "profile", "page.tsx"), EMPTY_PAGE); + + invalidateAppRouteCache(); + const routes = await appRouter(appDir); + const patterns = routes.map((route) => route.pattern); + + expect(patterns).toContain("/inbox"); + expect(patterns).not.toContain("/inbox/profile"); + expect(matchAppRoute("/inbox/profile", routes)).toBeNull(); + }); + }); + it("rejects non-terminal catch-all intercept targets", async () => { await withTempDir("vinext-app-intercept-nonterminal-catchall-", async (tmpDir) => { const appDir = path.join(tmpDir, "app");