diff --git a/contributors.yml b/contributors.yml
index bc025058fe8..dcf809c2ed9 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -343,6 +343,7 @@
- philandstuff
- phishy
- plastic041
+- pokedotdev
- plondon
- princerajroy
- prvnbist
diff --git a/integration/multiple-pathless-routes-test.ts b/integration/multiple-pathless-routes-test.ts
new file mode 100644
index 00000000000..daa760c688d
--- /dev/null
+++ b/integration/multiple-pathless-routes-test.ts
@@ -0,0 +1,91 @@
+import { test, expect } from "@playwright/test";
+
+import type { Fixture, AppFixture } from "./helpers/create-fixture";
+import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
+
+let fixture: Fixture;
+let appFixture: AppFixture;
+
+const TEXTS = {
+ SLUG_LAYOUT: "Layout: slug",
+ FOO_LAYOUT: "Layout: foo",
+ FOO_CHILD: "deep foo",
+ SLUG_CHILD: "Slug index from foo layout",
+ BAR_LAYOUT: "Layout: bar",
+ BAR_CHILD: "deep bar",
+} as const
+
+test.beforeAll(async () => {
+ fixture = await createFixture({
+ files: {
+ "app/routes/slug.jsx": js`
+ import { Outlet } from "@remix-run/react";
+ export default () => {
+ return (
+
+
${TEXTS.SLUG_LAYOUT}
+
+
+ )
+ }
+ `,
+ "app/routes/slug/__foo.jsx": js`
+ import { Outlet } from "@remix-run/react";
+ export default () => {
+ return (
+
+
${TEXTS.FOO_LAYOUT}
+
+
+ )
+ }
+ `,
+ "app/routes/slug/__foo/foo.jsx": js`
+ export default () => ${TEXTS.FOO_CHILD}
;
+ `,
+ "app/routes/slug/__foo/index.jsx": js`
+ export default () => ${TEXTS.SLUG_CHILD}
;
+ `,
+ "app/routes/slug/__bar.jsx": js`
+ import { Outlet } from "@remix-run/react";
+ export default () => {
+ return (
+
+
${TEXTS.BAR_LAYOUT}
+
+
+ )
+ }
+ `,
+ "app/routes/slug/__bar/bar.jsx": js`
+ export default () => ${TEXTS.BAR_CHILD}
;
+ `,
+ },
+ });
+
+ appFixture = await createAppFixture(fixture);
+});
+
+test.afterAll(async () => appFixture.close());
+
+test("slug index & foo layout", async () => {
+ let res = await fixture.requestDocument("/slug");
+ let text = await res.text();
+ expect(text).toMatch(TEXTS.SLUG_LAYOUT);
+ expect(text).toMatch(TEXTS.FOO_LAYOUT);
+ expect(text).toMatch(TEXTS.SLUG_CHILD);
+});
+
+test("foo layout", async () => {
+ let res = await fixture.requestDocument("/slug/foo");
+ let text = await res.text();
+ expect(text).toMatch(TEXTS.FOO_LAYOUT);
+ expect(text).toMatch(TEXTS.FOO_CHILD);
+});
+
+test("bar layout", async () => {
+ let res = await fixture.requestDocument("/slug/bar");
+ let text = await res.text();
+ expect(text).toMatch(TEXTS.BAR_LAYOUT);
+ expect(text).toMatch(TEXTS.BAR_CHILD);
+});
diff --git a/packages/remix-dev/config/routesConvention.ts b/packages/remix-dev/config/routesConvention.ts
index 4fb5fc3211e..89af8517104 100644
--- a/packages/remix-dev/config/routesConvention.ts
+++ b/packages/remix-dev/config/routesConvention.ts
@@ -62,13 +62,19 @@ export function defineConventionalRoutes(
);
for (let routeId of childRouteIds) {
- let routePath: string | undefined = createRoutePath(
- routeId.slice((parentId || "routes").length + 1)
- );
+ let lastSegment = routeId.slice((parentId || "routes").length + 1);
+ let routePath: string | undefined = createRoutePath(lastSegment);
- let isIndexRoute = routeId.endsWith("/index");
+ let isIndexRoute = lastSegment === "index";
+ let isLayoutRoute = lastSegment.startsWith("__");
let fullPath = createRoutePath(routeId.slice("routes".length + 1));
- let uniqueRouteId = (fullPath || "") + (isIndexRoute ? "?index" : "");
+ let uniqueRouteId = fullPath || "";
+
+ if (isIndexRoute) {
+ uniqueRouteId += "?index";
+ } else if (isLayoutRoute) {
+ uniqueRouteId += lastSegment;
+ }
if (uniqueRouteId) {
if (uniqueRoutes.has(uniqueRouteId)) {