Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dynamic routes with pages under index folder #32440

Merged
merged 6 commits into from
Dec 13, 2021

Conversation

huozhi
Copy link
Member

@huozhi huozhi commented Dec 13, 2021

Fixes incorrect generated manifest and generated directory for index/[...dynamic] pages

Too much normalizing adding extra index/ prefix to index/[...dynamic] routes which lead to the incorrected generated routes like .next/server/pages/index/index/index/[...dynamic]

Bug

Fixes https://github.com/vercel/customer-issues/issues/146

  • Related issues linked using fixes #number
  • Integration tests added
  • Errors have helpful link attached, see contributing.md

@ijjk ijjk added created-by: Next.js team PRs by the Next.js team. type: next labels Dec 13, 2021
@huozhi huozhi force-pushed the fix/index-dynamic-routes branch from 57ad0d4 to 3b49034 Compare December 13, 2021 10:51
@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@huozhi huozhi force-pushed the fix/index-dynamic-routes branch from 2f29e0f to 668a33d Compare December 13, 2021 14:41
@ijjk

This comment has been minimized.

@huozhi huozhi marked this pull request as ready for review December 13, 2021 15:16
Co-authored-by: JJ Kasper <jj@jjsweb.site>
@huozhi huozhi requested a review from ijjk December 13, 2021 18:37
ijjk
ijjk previously approved these changes Dec 13, 2021
@ijjk
Copy link
Member

ijjk commented Dec 13, 2021

Failing test suites

Commit: 1676ece

test/integration/react-streaming-and-server-components/test/index.test.js

  • concurrentFeatures - dev > should support next/link
Expand output

● concurrentFeatures - dev › should support next/link

expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: undefined

  259 |     )
  260 |     const dynamicRouteHTML2 = await renderViaHTTP(
> 261 |       context.appPort,
      |                       ^
  262 |       '/routes/dynamic2'
  263 |     )
  264 |

  at Object.<anonymous> (integration/react-streaming-and-server-components/test/index.test.js:261:56)

@ijjk

This comment has been minimized.

@ijjk
Copy link
Member

ijjk commented Dec 13, 2021

Stats from current PR

Default Build (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
buildDuration 18.1s 18.4s ⚠️ +268ms
buildDurationCached 3.5s 3.4s -74ms
nodeModulesSize 350 MB 350 MB ⚠️ +701 B
Page Load Tests Overall increase ✓
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
/ failed reqs 0 0
/ total time (seconds) 2.872 2.943 ⚠️ +0.07
/ avg req/sec 870.56 849.33 ⚠️ -21.23
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.367 1.337 -0.03
/error-in-render avg req/sec 1829.07 1869.98 +40.91
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
450.HASH.js gzip 179 B 179 B
framework-HASH.js gzip 42.2 kB 42.2 kB
main-HASH.js gzip 28.9 kB 30.2 kB ⚠️ +1.21 kB
webpack-HASH.js gzip 1.45 kB 1.45 kB
Overall change 72.8 kB 74 kB ⚠️ +1.21 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
_app-HASH.js gzip 1.37 kB 1.37 kB
_error-HASH.js gzip 194 B 194 B
amp-HASH.js gzip 312 B 312 B
css-HASH.js gzip 326 B 326 B
dynamic-HASH.js gzip 2.39 kB 2.39 kB
head-HASH.js gzip 350 B 350 B
hooks-HASH.js gzip 919 B 919 B
image-HASH.js gzip 4.73 kB 4.73 kB
index-HASH.js gzip 263 B 263 B
link-HASH.js gzip 2.13 kB 2.13 kB
routerDirect..HASH.js gzip 321 B 321 B
script-HASH.js gzip 383 B 383 B
withRouter-HASH.js gzip 318 B 318 B
85e02e95b279..7e3.css gzip 107 B 107 B
Overall change 14.1 kB 14.1 kB
Client Build Manifests
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
_buildManifest.js gzip 459 B 459 B
Overall change 459 B 459 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
index.html gzip 531 B 532 B ⚠️ +1 B
link.html gzip 545 B 547 B ⚠️ +2 B
withRouter.html gzip 526 B 527 B ⚠️ +1 B
Overall change 1.6 kB 1.61 kB ⚠️ +4 B

Diffs

Diff for main-HASH.js
@@ -6525,6 +6525,55 @@
       /***/
     },
 
+    /***/ 418: /***/ function(
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true
+      });
+      Object.defineProperty(exports, "getMiddlewareRegex", {
+        enumerable: true,
+        get: function get() {
+          return _getMiddlewareRegex.getMiddlewareRegex;
+        }
+      });
+      Object.defineProperty(exports, "getRouteMatcher", {
+        enumerable: true,
+        get: function get() {
+          return _routeMatcher.getRouteMatcher;
+        }
+      });
+      Object.defineProperty(exports, "getRouteRegex", {
+        enumerable: true,
+        get: function get() {
+          return _routeRegex.getRouteRegex;
+        }
+      });
+      Object.defineProperty(exports, "getSortedRoutes", {
+        enumerable: true,
+        get: function get() {
+          return _sortedRoutes.getSortedRoutes;
+        }
+      });
+      Object.defineProperty(exports, "isDynamicRoute", {
+        enumerable: true,
+        get: function get() {
+          return _isDynamic.isDynamicRoute;
+        }
+      });
+      var _getMiddlewareRegex = __webpack_require__(9820);
+      var _routeMatcher = __webpack_require__(3888);
+      var _routeRegex = __webpack_require__(4095);
+      var _sortedRoutes = __webpack_require__(3907);
+      var _isDynamic = __webpack_require__(8689); //# sourceMappingURL=index.js.map
+
+      /***/
+    },
+
     /***/ 8689: /***/ function(__unused_webpack_module, exports) {
       "use strict";
 
@@ -6850,6 +6899,345 @@
       /***/
     },
 
+    /***/ 3907: /***/ function(__unused_webpack_module, exports) {
+      "use strict";
+
+      function _arrayLikeToArray(arr, len) {
+        if (len == null || len > arr.length) len = arr.length;
+        for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+        return arr2;
+      }
+      function _arrayWithoutHoles(arr) {
+        if (Array.isArray(arr)) return _arrayLikeToArray(arr);
+      }
+      function _classCallCheck(instance, Constructor) {
+        if (!(instance instanceof Constructor)) {
+          throw new TypeError("Cannot call a class as a function");
+        }
+      }
+      function _defineProperties(target, props) {
+        for (var i = 0; i < props.length; i++) {
+          var descriptor = props[i];
+          descriptor.enumerable = descriptor.enumerable || false;
+          descriptor.configurable = true;
+          if ("value" in descriptor) descriptor.writable = true;
+          Object.defineProperty(target, descriptor.key, descriptor);
+        }
+      }
+      function _createClass(Constructor, protoProps, staticProps) {
+        if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+        if (staticProps) _defineProperties(Constructor, staticProps);
+        return Constructor;
+      }
+      function _iterableToArray(iter) {
+        if (
+          (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null) ||
+          iter["@@iterator"] != null
+        )
+          return Array.from(iter);
+      }
+      function _nonIterableSpread() {
+        throw new TypeError(
+          "Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
+        );
+      }
+      function _toConsumableArray(arr) {
+        return (
+          _arrayWithoutHoles(arr) ||
+          _iterableToArray(arr) ||
+          _unsupportedIterableToArray(arr) ||
+          _nonIterableSpread()
+        );
+      }
+      function _unsupportedIterableToArray(o, minLen) {
+        if (!o) return;
+        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+        var n = Object.prototype.toString.call(o).slice(8, -1);
+        if (n === "Object" && o.constructor) n = o.constructor.name;
+        if (n === "Map" || n === "Set") return Array.from(n);
+        if (
+          n === "Arguments" ||
+          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
+        )
+          return _arrayLikeToArray(o, minLen);
+      }
+      Object.defineProperty(exports, "__esModule", {
+        value: true
+      });
+      exports.getSortedRoutes = getSortedRoutes;
+      var UrlNode = /*#__PURE__*/ (function() {
+        function UrlNode() {
+          _classCallCheck(this, UrlNode);
+          this.placeholder = true;
+          this.children = new Map();
+          this.slugName = null;
+          this.restSlugName = null;
+          this.optionalRestSlugName = null;
+        }
+        _createClass(UrlNode, [
+          {
+            key: "insert",
+            value: function insert(urlPath) {
+              this._insert(urlPath.split("/").filter(Boolean), [], false);
+            }
+          },
+          {
+            key: "smoosh",
+            value: function smoosh() {
+              return this._smoosh();
+            }
+          },
+          {
+            key: "_smoosh",
+            value: function _smoosh() {
+              var prefix =
+                arguments.length > 0 && arguments[0] !== void 0
+                  ? arguments[0]
+                  : "/";
+              var _this = this;
+              var childrenPaths = _toConsumableArray(
+                this.children.keys()
+              ).sort();
+              if (this.slugName !== null) {
+                childrenPaths.splice(childrenPaths.indexOf("[]"), 1);
+              }
+              if (this.restSlugName !== null) {
+                childrenPaths.splice(childrenPaths.indexOf("[...]"), 1);
+              }
+              if (this.optionalRestSlugName !== null) {
+                childrenPaths.splice(childrenPaths.indexOf("[[...]]"), 1);
+              }
+              var routes = childrenPaths
+                .map(function(c) {
+                  return _this.children
+                    .get(c)
+                    ._smoosh("".concat(prefix).concat(c, "/"));
+                })
+                .reduce(function(prev, curr) {
+                  return _toConsumableArray(prev).concat(
+                    _toConsumableArray(curr)
+                  );
+                }, []);
+              if (this.slugName !== null) {
+                var _routes;
+                (_routes = routes).push.apply(
+                  _routes,
+                  _toConsumableArray(
+                    this.children
+                      .get("[]")
+                      ._smoosh(
+                        "".concat(prefix, "[").concat(this.slugName, "]/")
+                      )
+                  )
+                );
+              }
+              if (!this.placeholder) {
+                var r = prefix === "/" ? "/" : prefix.slice(0, -1);
+                if (this.optionalRestSlugName != null) {
+                  throw new Error(
+                    'You cannot define a route with the same specificity as a optional catch-all route ("'
+                      .concat(r, '" and "')
+                      .concat(r, "[[...")
+                      .concat(this.optionalRestSlugName, ']]").')
+                  );
+                }
+                routes.unshift(r);
+              }
+              if (this.restSlugName !== null) {
+                var _routes1;
+                (_routes1 = routes).push.apply(
+                  _routes1,
+                  _toConsumableArray(
+                    this.children
+                      .get("[...]")
+                      ._smoosh(
+                        ""
+                          .concat(prefix, "[...")
+                          .concat(this.restSlugName, "]/")
+                      )
+                  )
+                );
+              }
+              if (this.optionalRestSlugName !== null) {
+                var _routes2;
+                (_routes2 = routes).push.apply(
+                  _routes2,
+                  _toConsumableArray(
+                    this.children
+                      .get("[[...]]")
+                      ._smoosh(
+                        ""
+                          .concat(prefix, "[[...")
+                          .concat(this.optionalRestSlugName, "]]/")
+                      )
+                  )
+                );
+              }
+              return routes;
+            }
+          },
+          {
+            key: "_insert",
+            value: function _insert(urlPaths, slugNames, isCatchAll) {
+              if (urlPaths.length === 0) {
+                this.placeholder = false;
+                return;
+              }
+              if (isCatchAll) {
+                throw new Error("Catch-all must be the last part of the URL.");
+              }
+              // The next segment in the urlPaths list
+              var nextSegment = urlPaths[0];
+              // Check if the segment matches `[something]`
+              if (nextSegment.startsWith("[") && nextSegment.endsWith("]")) {
+                var handleSlug = function handleSlug(previousSlug, nextSlug) {
+                  if (previousSlug !== null) {
+                    // If the specific segment already has a slug but the slug is not `something`
+                    // This prevents collisions like:
+                    // pages/[post]/index.js
+                    // pages/[id]/index.js
+                    // Because currently multiple dynamic params on the same segment level are not supported
+                    if (previousSlug !== nextSlug) {
+                      // TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment.
+                      throw new Error(
+                        "You cannot use different slug names for the same dynamic path ('"
+                          .concat(previousSlug, "' !== '")
+                          .concat(nextSlug, "').")
+                      );
+                    }
+                  }
+                  slugNames.forEach(function(slug) {
+                    if (slug === nextSlug) {
+                      throw new Error(
+                        'You cannot have the same slug name "'.concat(
+                          nextSlug,
+                          '" repeat within a single dynamic path'
+                        )
+                      );
+                    }
+                    if (
+                      slug.replace(/\W/g, "") === nextSegment.replace(/\W/g, "")
+                    ) {
+                      throw new Error(
+                        'You cannot have the slug names "'
+                          .concat(slug, '" and "')
+                          .concat(
+                            nextSlug,
+                            '" differ only by non-word symbols within a single dynamic path'
+                          )
+                      );
+                    }
+                  });
+                  slugNames.push(nextSlug);
+                };
+                // Strip `[` and `]`, leaving only `something`
+                var segmentName = nextSegment.slice(1, -1);
+                var isOptional = false;
+                if (segmentName.startsWith("[") && segmentName.endsWith("]")) {
+                  // Strip optional `[` and `]`, leaving only `something`
+                  segmentName = segmentName.slice(1, -1);
+                  isOptional = true;
+                }
+                if (segmentName.startsWith("...")) {
+                  // Strip `...`, leaving only `something`
+                  segmentName = segmentName.substring(3);
+                  isCatchAll = true;
+                }
+                if (segmentName.startsWith("[") || segmentName.endsWith("]")) {
+                  throw new Error(
+                    "Segment names may not start or end with extra brackets ('".concat(
+                      segmentName,
+                      "')."
+                    )
+                  );
+                }
+                if (segmentName.startsWith(".")) {
+                  throw new Error(
+                    "Segment names may not start with erroneous periods ('".concat(
+                      segmentName,
+                      "')."
+                    )
+                  );
+                }
+                if (isCatchAll) {
+                  if (isOptional) {
+                    if (this.restSlugName != null) {
+                      throw new Error(
+                        'You cannot use both an required and optional catch-all route at the same level ("[...'
+                          .concat(this.restSlugName, ']" and "')
+                          .concat(urlPaths[0], '" ).')
+                      );
+                    }
+                    handleSlug(this.optionalRestSlugName, segmentName);
+                    // slugName is kept as it can only be one particular slugName
+                    this.optionalRestSlugName = segmentName;
+                    // nextSegment is overwritten to [[...]] so that it can later be sorted specifically
+                    nextSegment = "[[...]]";
+                  } else {
+                    if (this.optionalRestSlugName != null) {
+                      throw new Error(
+                        'You cannot use both an optional and required catch-all route at the same level ("[[...'
+                          .concat(this.optionalRestSlugName, ']]" and "')
+                          .concat(urlPaths[0], '").')
+                      );
+                    }
+                    handleSlug(this.restSlugName, segmentName);
+                    // slugName is kept as it can only be one particular slugName
+                    this.restSlugName = segmentName;
+                    // nextSegment is overwritten to [...] so that it can later be sorted specifically
+                    nextSegment = "[...]";
+                  }
+                } else {
+                  if (isOptional) {
+                    throw new Error(
+                      'Optional route parameters are not yet supported ("'.concat(
+                        urlPaths[0],
+                        '").'
+                      )
+                    );
+                  }
+                  handleSlug(this.slugName, segmentName);
+                  // slugName is kept as it can only be one particular slugName
+                  this.slugName = segmentName;
+                  // nextSegment is overwritten to [] so that it can later be sorted specifically
+                  nextSegment = "[]";
+                }
+              }
+              // If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
+              if (!this.children.has(nextSegment)) {
+                this.children.set(nextSegment, new UrlNode());
+              }
+              this.children
+                .get(nextSegment)
+                ._insert(urlPaths.slice(1), slugNames, isCatchAll);
+            }
+          }
+        ]);
+        return UrlNode;
+      })();
+      function getSortedRoutes(normalizedPages) {
+        // First the UrlNode is created, and every UrlNode can have only 1 dynamic segment
+        // Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js
+        // Only 1 dynamic segment per nesting level
+        // So in the case that is test/integration/dynamic-routing it'll be this:
+        // pages/[post]/comments.js
+        // pages/blog/[post]/comment/[id].js
+        // Both are fine because `pages/[post]` and `pages/blog` are on the same level
+        // So in this case `UrlNode` created here has `this.slugName === 'post'`
+        // And since your PR passed through `slugName` as an array basically it'd including it in too many possibilities
+        // Instead what has to be passed through is the upwards path's dynamic names
+        var root = new UrlNode();
+        // Here the `root` gets injected multiple paths, and insert will break them up into sublevels
+        normalizedPages.forEach(function(pagePath) {
+          return root.insert(pagePath);
+        });
+        // Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
+        return root.smoosh();
+      } //# sourceMappingURL=sorted-routes.js.map
+
+      /***/
+    },
+
     /***/ 8027: /***/ function(__unused_webpack_module, exports) {
       "use strict";
 
@@ -7913,7 +8301,11 @@
       /***/
     },
 
-    /***/ 4522: /***/ function(__unused_webpack_module, exports) {
+    /***/ 4522: /***/ function(
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -7921,12 +8313,13 @@
       });
       exports.normalizePathSep = normalizePathSep;
       exports.denormalizePagePath = denormalizePagePath;
+      var _utils = __webpack_require__(418);
       function normalizePathSep(path) {
         return path.replace(/\\/g, "/");
       }
       function denormalizePagePath(page) {
         page = normalizePathSep(page);
-        if (page.startsWith("/index/")) {
+        if (page.startsWith("/index/") && !(0, _utils).isDynamicRoute(page)) {
           page = page.slice(6);
         } else if (page === "/index") {
           page = "/";
Diff for index.html
@@ -19,7 +19,7 @@
       defer=""
     ></script>
     <script
-      src="/_next/static/chunks/main-265c59c213a49f1c.js"
+      src="/_next/static/chunks/main-92a7810899e6ca71.js"
       defer=""
     ></script>
     <script
Diff for link.html
@@ -19,7 +19,7 @@
       defer=""
     ></script>
     <script
-      src="/_next/static/chunks/main-265c59c213a49f1c.js"
+      src="/_next/static/chunks/main-92a7810899e6ca71.js"
       defer=""
     ></script>
     <script
Diff for withRouter.html
@@ -19,7 +19,7 @@
       defer=""
     ></script>
     <script
-      src="/_next/static/chunks/main-265c59c213a49f1c.js"
+      src="/_next/static/chunks/main-92a7810899e6ca71.js"
       defer=""
     ></script>
     <script

Default Build with SWC (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
buildDuration 19.5s 19.6s ⚠️ +49ms
buildDurationCached 3.5s 3.4s -149ms
nodeModulesSize 350 MB 350 MB ⚠️ +701 B
Page Load Tests Overall increase ✓
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
/ failed reqs 0 0
/ total time (seconds) 2.917 2.924 ⚠️ +0.01
/ avg req/sec 857.17 855 ⚠️ -2.17
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.344 1.301 -0.04
/error-in-render avg req/sec 1860.72 1921.01 +60.29
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
450.HASH.js gzip 179 B 179 B
framework-HASH.js gzip 42.3 kB 42.3 kB
main-HASH.js gzip 29.1 kB 30.3 kB ⚠️ +1.22 kB
webpack-HASH.js gzip 1.44 kB 1.44 kB
Overall change 73 kB 74.2 kB ⚠️ +1.22 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
_app-HASH.js gzip 1.35 kB 1.35 kB
_error-HASH.js gzip 180 B 180 B
amp-HASH.js gzip 305 B 305 B
css-HASH.js gzip 321 B 321 B
dynamic-HASH.js gzip 2.39 kB 2.39 kB
head-HASH.js gzip 342 B 342 B
hooks-HASH.js gzip 906 B 906 B
image-HASH.js gzip 4.75 kB 4.75 kB
index-HASH.js gzip 256 B 256 B
link-HASH.js gzip 2.19 kB 2.19 kB
routerDirect..HASH.js gzip 314 B 314 B
script-HASH.js gzip 375 B 375 B
withRouter-HASH.js gzip 309 B 309 B
85e02e95b279..7e3.css gzip 107 B 107 B
Overall change 14.1 kB 14.1 kB
Client Build Manifests
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
_buildManifest.js gzip 458 B 458 B
Overall change 458 B 458 B
Rendered Page Sizes
vercel/next.js canary huozhi/next.js fix/index-dynamic-routes Change
index.html gzip 533 B 533 B
link.html gzip 546 B 546 B
withRouter.html gzip 527 B 527 B
Overall change 1.61 kB 1.61 kB

Diffs

Diff for main-HASH.js
@@ -6525,6 +6525,55 @@
       /***/
     },
 
+    /***/ 418: /***/ function(
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true
+      });
+      Object.defineProperty(exports, "getMiddlewareRegex", {
+        enumerable: true,
+        get: function get() {
+          return _getMiddlewareRegex.getMiddlewareRegex;
+        }
+      });
+      Object.defineProperty(exports, "getRouteMatcher", {
+        enumerable: true,
+        get: function get() {
+          return _routeMatcher.getRouteMatcher;
+        }
+      });
+      Object.defineProperty(exports, "getRouteRegex", {
+        enumerable: true,
+        get: function get() {
+          return _routeRegex.getRouteRegex;
+        }
+      });
+      Object.defineProperty(exports, "getSortedRoutes", {
+        enumerable: true,
+        get: function get() {
+          return _sortedRoutes.getSortedRoutes;
+        }
+      });
+      Object.defineProperty(exports, "isDynamicRoute", {
+        enumerable: true,
+        get: function get() {
+          return _isDynamic.isDynamicRoute;
+        }
+      });
+      var _getMiddlewareRegex = __webpack_require__(9820);
+      var _routeMatcher = __webpack_require__(3888);
+      var _routeRegex = __webpack_require__(4095);
+      var _sortedRoutes = __webpack_require__(3907);
+      var _isDynamic = __webpack_require__(8689); //# sourceMappingURL=index.js.map
+
+      /***/
+    },
+
     /***/ 8689: /***/ function(__unused_webpack_module, exports) {
       "use strict";
 
@@ -6850,6 +6899,345 @@
       /***/
     },
 
+    /***/ 3907: /***/ function(__unused_webpack_module, exports) {
+      "use strict";
+
+      function _arrayLikeToArray(arr, len) {
+        if (len == null || len > arr.length) len = arr.length;
+        for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+        return arr2;
+      }
+      function _arrayWithoutHoles(arr) {
+        if (Array.isArray(arr)) return _arrayLikeToArray(arr);
+      }
+      function _classCallCheck(instance, Constructor) {
+        if (!(instance instanceof Constructor)) {
+          throw new TypeError("Cannot call a class as a function");
+        }
+      }
+      function _defineProperties(target, props) {
+        for (var i = 0; i < props.length; i++) {
+          var descriptor = props[i];
+          descriptor.enumerable = descriptor.enumerable || false;
+          descriptor.configurable = true;
+          if ("value" in descriptor) descriptor.writable = true;
+          Object.defineProperty(target, descriptor.key, descriptor);
+        }
+      }
+      function _createClass(Constructor, protoProps, staticProps) {
+        if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+        if (staticProps) _defineProperties(Constructor, staticProps);
+        return Constructor;
+      }
+      function _iterableToArray(iter) {
+        if (
+          (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null) ||
+          iter["@@iterator"] != null
+        )
+          return Array.from(iter);
+      }
+      function _nonIterableSpread() {
+        throw new TypeError(
+          "Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
+        );
+      }
+      function _toConsumableArray(arr) {
+        return (
+          _arrayWithoutHoles(arr) ||
+          _iterableToArray(arr) ||
+          _unsupportedIterableToArray(arr) ||
+          _nonIterableSpread()
+        );
+      }
+      function _unsupportedIterableToArray(o, minLen) {
+        if (!o) return;
+        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+        var n = Object.prototype.toString.call(o).slice(8, -1);
+        if (n === "Object" && o.constructor) n = o.constructor.name;
+        if (n === "Map" || n === "Set") return Array.from(n);
+        if (
+          n === "Arguments" ||
+          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
+        )
+          return _arrayLikeToArray(o, minLen);
+      }
+      Object.defineProperty(exports, "__esModule", {
+        value: true
+      });
+      exports.getSortedRoutes = getSortedRoutes;
+      var UrlNode = /*#__PURE__*/ (function() {
+        function UrlNode() {
+          _classCallCheck(this, UrlNode);
+          this.placeholder = true;
+          this.children = new Map();
+          this.slugName = null;
+          this.restSlugName = null;
+          this.optionalRestSlugName = null;
+        }
+        _createClass(UrlNode, [
+          {
+            key: "insert",
+            value: function insert(urlPath) {
+              this._insert(urlPath.split("/").filter(Boolean), [], false);
+            }
+          },
+          {
+            key: "smoosh",
+            value: function smoosh() {
+              return this._smoosh();
+            }
+          },
+          {
+            key: "_smoosh",
+            value: function _smoosh() {
+              var prefix =
+                arguments.length > 0 && arguments[0] !== void 0
+                  ? arguments[0]
+                  : "/";
+              var _this = this;
+              var childrenPaths = _toConsumableArray(
+                this.children.keys()
+              ).sort();
+              if (this.slugName !== null) {
+                childrenPaths.splice(childrenPaths.indexOf("[]"), 1);
+              }
+              if (this.restSlugName !== null) {
+                childrenPaths.splice(childrenPaths.indexOf("[...]"), 1);
+              }
+              if (this.optionalRestSlugName !== null) {
+                childrenPaths.splice(childrenPaths.indexOf("[[...]]"), 1);
+              }
+              var routes = childrenPaths
+                .map(function(c) {
+                  return _this.children
+                    .get(c)
+                    ._smoosh("".concat(prefix).concat(c, "/"));
+                })
+                .reduce(function(prev, curr) {
+                  return _toConsumableArray(prev).concat(
+                    _toConsumableArray(curr)
+                  );
+                }, []);
+              if (this.slugName !== null) {
+                var _routes;
+                (_routes = routes).push.apply(
+                  _routes,
+                  _toConsumableArray(
+                    this.children
+                      .get("[]")
+                      ._smoosh(
+                        "".concat(prefix, "[").concat(this.slugName, "]/")
+                      )
+                  )
+                );
+              }
+              if (!this.placeholder) {
+                var r = prefix === "/" ? "/" : prefix.slice(0, -1);
+                if (this.optionalRestSlugName != null) {
+                  throw new Error(
+                    'You cannot define a route with the same specificity as a optional catch-all route ("'
+                      .concat(r, '" and "')
+                      .concat(r, "[[...")
+                      .concat(this.optionalRestSlugName, ']]").')
+                  );
+                }
+                routes.unshift(r);
+              }
+              if (this.restSlugName !== null) {
+                var _routes1;
+                (_routes1 = routes).push.apply(
+                  _routes1,
+                  _toConsumableArray(
+                    this.children
+                      .get("[...]")
+                      ._smoosh(
+                        ""
+                          .concat(prefix, "[...")
+                          .concat(this.restSlugName, "]/")
+                      )
+                  )
+                );
+              }
+              if (this.optionalRestSlugName !== null) {
+                var _routes2;
+                (_routes2 = routes).push.apply(
+                  _routes2,
+                  _toConsumableArray(
+                    this.children
+                      .get("[[...]]")
+                      ._smoosh(
+                        ""
+                          .concat(prefix, "[[...")
+                          .concat(this.optionalRestSlugName, "]]/")
+                      )
+                  )
+                );
+              }
+              return routes;
+            }
+          },
+          {
+            key: "_insert",
+            value: function _insert(urlPaths, slugNames, isCatchAll) {
+              if (urlPaths.length === 0) {
+                this.placeholder = false;
+                return;
+              }
+              if (isCatchAll) {
+                throw new Error("Catch-all must be the last part of the URL.");
+              }
+              // The next segment in the urlPaths list
+              var nextSegment = urlPaths[0];
+              // Check if the segment matches `[something]`
+              if (nextSegment.startsWith("[") && nextSegment.endsWith("]")) {
+                var handleSlug = function handleSlug(previousSlug, nextSlug) {
+                  if (previousSlug !== null) {
+                    // If the specific segment already has a slug but the slug is not `something`
+                    // This prevents collisions like:
+                    // pages/[post]/index.js
+                    // pages/[id]/index.js
+                    // Because currently multiple dynamic params on the same segment level are not supported
+                    if (previousSlug !== nextSlug) {
+                      // TODO: This error seems to be confusing for users, needs an error link, the description can be based on above comment.
+                      throw new Error(
+                        "You cannot use different slug names for the same dynamic path ('"
+                          .concat(previousSlug, "' !== '")
+                          .concat(nextSlug, "').")
+                      );
+                    }
+                  }
+                  slugNames.forEach(function(slug) {
+                    if (slug === nextSlug) {
+                      throw new Error(
+                        'You cannot have the same slug name "'.concat(
+                          nextSlug,
+                          '" repeat within a single dynamic path'
+                        )
+                      );
+                    }
+                    if (
+                      slug.replace(/\W/g, "") === nextSegment.replace(/\W/g, "")
+                    ) {
+                      throw new Error(
+                        'You cannot have the slug names "'
+                          .concat(slug, '" and "')
+                          .concat(
+                            nextSlug,
+                            '" differ only by non-word symbols within a single dynamic path'
+                          )
+                      );
+                    }
+                  });
+                  slugNames.push(nextSlug);
+                };
+                // Strip `[` and `]`, leaving only `something`
+                var segmentName = nextSegment.slice(1, -1);
+                var isOptional = false;
+                if (segmentName.startsWith("[") && segmentName.endsWith("]")) {
+                  // Strip optional `[` and `]`, leaving only `something`
+                  segmentName = segmentName.slice(1, -1);
+                  isOptional = true;
+                }
+                if (segmentName.startsWith("...")) {
+                  // Strip `...`, leaving only `something`
+                  segmentName = segmentName.substring(3);
+                  isCatchAll = true;
+                }
+                if (segmentName.startsWith("[") || segmentName.endsWith("]")) {
+                  throw new Error(
+                    "Segment names may not start or end with extra brackets ('".concat(
+                      segmentName,
+                      "')."
+                    )
+                  );
+                }
+                if (segmentName.startsWith(".")) {
+                  throw new Error(
+                    "Segment names may not start with erroneous periods ('".concat(
+                      segmentName,
+                      "')."
+                    )
+                  );
+                }
+                if (isCatchAll) {
+                  if (isOptional) {
+                    if (this.restSlugName != null) {
+                      throw new Error(
+                        'You cannot use both an required and optional catch-all route at the same level ("[...'
+                          .concat(this.restSlugName, ']" and "')
+                          .concat(urlPaths[0], '" ).')
+                      );
+                    }
+                    handleSlug(this.optionalRestSlugName, segmentName);
+                    // slugName is kept as it can only be one particular slugName
+                    this.optionalRestSlugName = segmentName;
+                    // nextSegment is overwritten to [[...]] so that it can later be sorted specifically
+                    nextSegment = "[[...]]";
+                  } else {
+                    if (this.optionalRestSlugName != null) {
+                      throw new Error(
+                        'You cannot use both an optional and required catch-all route at the same level ("[[...'
+                          .concat(this.optionalRestSlugName, ']]" and "')
+                          .concat(urlPaths[0], '").')
+                      );
+                    }
+                    handleSlug(this.restSlugName, segmentName);
+                    // slugName is kept as it can only be one particular slugName
+                    this.restSlugName = segmentName;
+                    // nextSegment is overwritten to [...] so that it can later be sorted specifically
+                    nextSegment = "[...]";
+                  }
+                } else {
+                  if (isOptional) {
+                    throw new Error(
+                      'Optional route parameters are not yet supported ("'.concat(
+                        urlPaths[0],
+                        '").'
+                      )
+                    );
+                  }
+                  handleSlug(this.slugName, segmentName);
+                  // slugName is kept as it can only be one particular slugName
+                  this.slugName = segmentName;
+                  // nextSegment is overwritten to [] so that it can later be sorted specifically
+                  nextSegment = "[]";
+                }
+              }
+              // If this UrlNode doesn't have the nextSegment yet we create a new child UrlNode
+              if (!this.children.has(nextSegment)) {
+                this.children.set(nextSegment, new UrlNode());
+              }
+              this.children
+                .get(nextSegment)
+                ._insert(urlPaths.slice(1), slugNames, isCatchAll);
+            }
+          }
+        ]);
+        return UrlNode;
+      })();
+      function getSortedRoutes(normalizedPages) {
+        // First the UrlNode is created, and every UrlNode can have only 1 dynamic segment
+        // Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js
+        // Only 1 dynamic segment per nesting level
+        // So in the case that is test/integration/dynamic-routing it'll be this:
+        // pages/[post]/comments.js
+        // pages/blog/[post]/comment/[id].js
+        // Both are fine because `pages/[post]` and `pages/blog` are on the same level
+        // So in this case `UrlNode` created here has `this.slugName === 'post'`
+        // And since your PR passed through `slugName` as an array basically it'd including it in too many possibilities
+        // Instead what has to be passed through is the upwards path's dynamic names
+        var root = new UrlNode();
+        // Here the `root` gets injected multiple paths, and insert will break them up into sublevels
+        normalizedPages.forEach(function(pagePath) {
+          return root.insert(pagePath);
+        });
+        // Smoosh will then sort those sublevels up to the point where you get the correct route definition priority
+        return root.smoosh();
+      } //# sourceMappingURL=sorted-routes.js.map
+
+      /***/
+    },
+
     /***/ 8027: /***/ function(__unused_webpack_module, exports) {
       "use strict";
 
@@ -7913,7 +8301,11 @@
       /***/
     },
 
-    /***/ 4522: /***/ function(__unused_webpack_module, exports) {
+    /***/ 4522: /***/ function(
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -7921,12 +8313,13 @@
       });
       exports.normalizePathSep = normalizePathSep;
       exports.denormalizePagePath = denormalizePagePath;
+      var _utils = __webpack_require__(418);
       function normalizePathSep(path) {
         return path.replace(/\\/g, "/");
       }
       function denormalizePagePath(page) {
         page = normalizePathSep(page);
-        if (page.startsWith("/index/")) {
+        if (page.startsWith("/index/") && !(0, _utils).isDynamicRoute(page)) {
           page = page.slice(6);
         } else if (page === "/index") {
           page = "/";
Diff for index.html
@@ -19,7 +19,7 @@
       defer=""
     ></script>
     <script
-      src="/_next/static/chunks/main-265c59c213a49f1c.js"
+      src="/_next/static/chunks/main-92a7810899e6ca71.js"
       defer=""
     ></script>
     <script
Diff for link.html
@@ -19,7 +19,7 @@
       defer=""
     ></script>
     <script
-      src="/_next/static/chunks/main-265c59c213a49f1c.js"
+      src="/_next/static/chunks/main-92a7810899e6ca71.js"
       defer=""
     ></script>
     <script
Diff for withRouter.html
@@ -19,7 +19,7 @@
       defer=""
     ></script>
     <script
-      src="/_next/static/chunks/main-265c59c213a49f1c.js"
+      src="/_next/static/chunks/main-92a7810899e6ca71.js"
       defer=""
     ></script>
     <script
Commit: 13775ab

@kodiakhq kodiakhq bot merged commit 10d814d into vercel:canary Dec 13, 2021
@huozhi huozhi deleted the fix/index-dynamic-routes branch December 13, 2021 20:41
@RizBizKits
Copy link

@huozhi can you confirm if this fix is available in v12.0.8-canary.5? I don't see it mentioned in the Release

@huozhi
Copy link
Member Author

huozhi commented Dec 14, 2021

@RizBizKits It's fixed after v12.0.8-canary.5, so would be released in v12.0.8-canary.6! 🙏

cdierkens pushed a commit to cdierkens/next.js that referenced this pull request Dec 20, 2021
Fixes incorrect generated manifest and generated directory for `index/[...dynamic]` pages

Too much normalizing adding extra `index/` prefix to `index/[...dynamic]` routes which lead to the incorrected generated routes like `.next/server/pages/index/index/index/[...dynamic]`

## Bug

Fixes https://github.com/vercel/customer-issues/issues/146

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`
@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants