KEMBAR78
Replace <link rel=stylesheet> client-side transitions with <style> tags by Timer · Pull Request #16581 · vercel/next.js · GitHub
Skip to content

Conversation

Timer
Copy link
Member

@Timer Timer commented Aug 26, 2020

This pull request replaces our client-side style transitions with <style> tags over async <link rel=stylesheet> tags. This should fix some edge cases users see with Chrome accidentally causing a FOUC.

This also removes the need to perform an async operation before starting the render, which should remove any perceivable navigation delay.


Fixes #16289

@ijjk
Copy link
Member

ijjk commented Aug 26, 2020

Failing test suites

Commit: f40d80d

test/integration/css-client-nav/test/index.test.js

  • CSS Module client-side navigation in Production > should be able to client-side navigate from blue to red
Expand output

● CSS Module client-side navigation in Production › should be able to client-side navigate from blue to red

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

Expected: 1
Received: 0

  91 |         `[].slice.call(document.querySelectorAll('link[rel="prefetch"][as="style"]')).map(e=>({href:e.href})).sort()`
  92 |       )
> 93 |       expect(result.length).toBe(1)
     |                             ^
  94 | 
  95 |       // Check that CSS was not loaded as script
  96 |       const cssPreloads = await browser.eval(

  at Object.<anonymous> (integration/css-client-nav/test/index.test.js:93:29)

test/integration/css/test/index.test.js

  • CSS Support > CSS Modules Composes Ordering > Production Mode > should have correct CSS injection order
  • CSS Support > CSS Modules Composes Ordering > Production Mode > should have correct color on index page (on nav from other)
Expand output

● CSS Support › CSS Modules Composes Ordering › Production Mode › should have correct CSS injection order

JavascriptError: javascript error: Cannot read property 'previousSibling' of null
  (Session info: headless chrome=84.0.4147.125)

  1134 |             await checkRedTitle(browser)
  1135 | 
> 1136 |             const newPrevSiblingHref = await browser.eval(
       |                                        ^
  1137 |               `document.querySelector('link[rel=stylesheet][data-n-p]').previousSibling.getAttribute('href')`
  1138 |             )
  1139 |             const newPageHref = await browser.eval(

  at Object.throwDecodedError (../node_modules/selenium-webdriver/lib/error.js:550:15)
  at parseHttpResponse (../node_modules/selenium-webdriver/lib/http.js:565:13)
  at Executor.execute (../node_modules/selenium-webdriver/lib/http.js:491:26)
      at runMicrotasks (<anonymous>)
  at thenableWebDriverProxy.execute (../node_modules/selenium-webdriver/lib/webdriver.js:700:17)
  at Object.<anonymous> (integration/css/test/index.test.js:1136:40)

● CSS Support › CSS Modules Composes Ordering › Production Mode › should have correct color on index page (on nav from other)

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

Expected: "rgb(255, 0, 0)"
Received: "rgb(0, 0, 0)"

  1071 |           `window.getComputedStyle(document.querySelector('#red-title')).color`
  1072 |         )
> 1073 |         expect(titleColor).toBe('rgb(255, 0, 0)')
       |                            ^
  1074 |       }
  1075 |       async function checkCssPreloadCount(browser) {
  1076 |         return Number(

  at checkRedTitle (integration/css/test/index.test.js:1073:28)
      at runMicrotasks (<anonymous>)
  at Object.<anonymous> (integration/css/test/index.test.js:1181:11)

@ijjk
Copy link
Member

ijjk commented Aug 26, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 12.2s 12s -237ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Page Load Tests Overall increase ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
/ failed reqs 0 0
/ total time (seconds) 2.185 2.138 -0.05
/ avg req/sec 1144.28 1169.49 +25.21
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.192 1.177 -0.01
/error-in-render avg req/sec 2098.07 2123.59 +25.52
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB 7.32 kB -20 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB 6.39 kB -4 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
index.html gzip 948 B 970 B ⚠️ +22 B
link.html gzip 954 B 977 B ⚠️ +23 B
withRouter.html gzip 940 B 963 B ⚠️ +23 B
Overall change 2.84 kB 2.91 kB ⚠️ +68 B

Diffs

Diff for main-209a935..118ae6828.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,14 +319,31 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = function looseToArray(input) {
+        return [].slice.call(input);
+      };
+
       var pageLoader = new _pageLoader["default"](
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(function(e) {
-            return e.getAttribute("href");
+        looseToArray(document.styleSheets)
+          .filter(function(el) {
+            return (
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+            );
+          })
+          .map(function(sheet) {
+            return {
+              href: sheet.ownerNode.getAttribute("href"),
+              text: looseToArray(sheet.cssRules)
+                .map(function(r) {
+                  return r.cssText;
+                })
+                .join("")
+            };
           })
       );
 
@@ -967,8 +984,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise(function(resolve, reject) {
           if (_lastRenderReject) {
             _lastRenderReject();
@@ -979,17 +996,15 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             resolve();
           };
 
-          renderPromiseReject = _lastRenderReject = function lastRenderReject() {
+          _lastRenderReject = function lastRenderReject() {
+            canceled = true;
             _lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -999,82 +1014,30 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(function(el) {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(function(e) {
-              return e.getAttribute("href");
+          var currentHrefs = new Set(
+            currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
             })
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(function(href) {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var _ref9 = (0, _pageLoader.createLink)(href, "stylesheet"),
-                _ref10 = _slicedToArray(_ref9, 2),
-                link = _ref10[0],
-                onload = _ref10[1];
-
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(function(_ref9) {
+            var href = _ref9.href,
+              text = _ref9.text;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required)["catch"](function() {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(function() {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -1083,45 +1046,60 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === _lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(
+              styleSheets.map(function(s) {
+                return s.href;
+              })
             );
-            var relocateHrefs = relocatePlaceholders.map(function(e) {
-              return e.getAttribute("data-n-staging");
-            });
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(function(el) {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
+            );
+            var currentHrefs = currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
+            }); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(function(_ref10) {
+                var href = _ref10.href;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(function(el) {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              function(el) {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -1139,29 +1117,12 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             null,
             /*#__PURE__*/ _react["default"].createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(function() {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === _lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(function() {
-              return (
-                // Wait for rendering to complete:
-                renderPromise
-              );
-            }), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
       function Root(_ref11) {
@@ -1617,12 +1578,9 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _createClass = __webpack_require__("W8MJ");
 
-      var _slicedToArray = __webpack_require__("J4zp");
-
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports["default"] = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1667,6 +1625,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1686,31 +1645,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise(function(res, rej) {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise(function(res, rej) {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var _createLink = createLink(href, rel, as),
-          _createLink2 = _slicedToArray(_createLink, 2),
-          link = _createLink2[0],
-          res = _createLink2[1];
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
 
-        document.head.appendChild(link);
-        return res;
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -2007,7 +1954,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                             // wait for these to resolve. To prevent an unhandled
                             // rejection, we swallow the error which is handled later in
                             // the rendering cycle (this is just a preload optimization).
-                            appendLink(d, relPreload, "style")["catch"](
+                            appendLink(d, relPreload, relPreloadStyle)["catch"](
                               function() {
                                 /* ignore preload error */
                               }
@@ -2064,20 +2011,44 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
+              function fetchStyleSheet(href) {
+                return fetch(href).then(function(res) {
+                  if (!res.ok) throw pageLoadError(href);
+                  return res.text().then(function(text) {
+                    return {
+                      href: href,
+                      text: text
+                    };
+                  });
+                });
+              }
+
               var promisedDeps = // Shared styles will already be on the page:
                 route === "/_app" || false // We use `style-loader` in development:
                   ? Promise.resolve([])
                   : route === this.initialPage
                   ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
                   : // test/integration/css-fixtures/hydrate-without-deps/
-                    this.getDependencies(route);
+                    this.getDependencies(route)
+                      .then(function(deps) {
+                        return deps.filter(function(d) {
+                          return d.endsWith(".css");
+                        });
+                      })
+                      .then(function(cssFiles) {
+                        return (
+                          // These files should've already been fetched by now, so this
+                          // should resolve pretty much instantly.
+                          Promise.all(
+                            cssFiles.map(function(d) {
+                              return fetchStyleSheet(d);
+                            })
+                          )
+                        );
+                      });
               promisedDeps.then(
                 function(deps) {
-                  return register(
-                    deps.filter(function(d) {
-                      return d.endsWith(".css");
-                    })
-                  );
+                  return register(deps);
                 },
                 function(error) {
                   _this5.pageCache[route] = {
@@ -2133,7 +2104,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         appendLink(
                           url,
                           relPrefetch,
-                          url.endsWith(".css") ? "style" : "script"
+                          url.endsWith(".css") ? relPreloadStyle : "script"
                         ),
                       true &&
                         !isDependency &&
Diff for main-be61079..e7.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,13 +239,25 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = input => [].slice.call(input);
+
       var pageLoader = new _pageLoader.default(
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(e => e.getAttribute("href"))
+        looseToArray(document.styleSheets)
+          .filter(
+            el =>
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+          )
+          .map(sheet => ({
+            href: sheet.ownerNode.getAttribute("href"),
+            text: looseToArray(sheet.cssRules)
+              .map(r => r.cssText)
+              .join("")
+          }))
       );
 
       var register = _ref => {
@@ -702,8 +714,8 @@
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise((resolve, reject) => {
           if (lastRenderReject) {
             lastRenderReject();
@@ -714,17 +726,15 @@
             resolve();
           };
 
-          renderPromiseReject = lastRenderReject = () => {
+          lastRenderReject = () => {
+            canceled = true;
             lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -734,79 +744,27 @@
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(el => {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(e => e.getAttribute("href"))
+          var currentHrefs = new Set(
+            currentStyleTags.map(tag => tag.getAttribute("data-n-href"))
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(href => {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var [link, onload] = (0, _pageLoader.createLink)(
-                href,
-                "stylesheet"
-              );
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(_ref7 => {
+            var { href, text } = _ref7;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required).catch(() => {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(() => {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -815,45 +773,56 @@
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(styleSheets.map(s => s.href));
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
             );
-            var relocateHrefs = relocatePlaceholders.map(e =>
-              e.getAttribute("data-n-staging")
-            );
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(el => {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentHrefs = currentStyleTags.map(tag =>
+              tag.getAttribute("data-n-href")
+            ); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(_ref8 => {
+                var { href } = _ref8;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(el => {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              el => {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -871,32 +840,16 @@
             null,
             /*#__PURE__*/ _react.default.createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(() => {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(
-              () =>
-                // Wait for rendering to complete:
-                renderPromise
-            ), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
-      function Root(_ref7) {
-        var { callback, children } = _ref7;
+      function Root(_ref9) {
+        var { callback, children } = _ref9;
 
         // We use `useLayoutEffect` to guarantee the callback is executed
         // as soon as React flushes the update.
@@ -1287,7 +1240,6 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1332,6 +1284,7 @@
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1351,27 +1304,19 @@
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise((res, rej) => {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise((res, rej) => {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var [link, res] = createLink(href, rel, as);
-        document.head.appendChild(link);
-        return res;
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
+
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -1617,7 +1562,7 @@
                         // wait for these to resolve. To prevent an unhandled
                         // rejection, we swallow the error which is handled later in
                         // the rendering cycle (this is just a preload optimization).
-                        appendLink(d, relPreload, "style").catch(() => {
+                        appendLink(d, relPreload, relPreloadStyle).catch(() => {
                           /* ignore preload error */
                         });
                       }
@@ -1666,15 +1611,32 @@
             var check;
           }
 
+          function fetchStyleSheet(href) {
+            return fetch(href).then(res => {
+              if (!res.ok) throw pageLoadError(href);
+              return res.text().then(text => ({
+                href,
+                text
+              }));
+            });
+          }
+
           var promisedDeps = // Shared styles will already be on the page:
             route === "/_app" || false // We use `style-loader` in development:
               ? Promise.resolve([])
               : route === this.initialPage
               ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
               : // test/integration/css-fixtures/hydrate-without-deps/
-                this.getDependencies(route);
+                this.getDependencies(route)
+                  .then(deps => deps.filter(d => d.endsWith(".css")))
+                  .then((
+                    cssFiles // These files should've already been fetched by now, so this
+                  ) =>
+                    // should resolve pretty much instantly.
+                    Promise.all(cssFiles.map(d => fetchStyleSheet(d)))
+                  );
           promisedDeps.then(
-            deps => register(deps.filter(d => d.endsWith(".css"))),
+            deps => register(deps),
             error => {
               this.pageCache[route] = {
                 error
@@ -1722,7 +1684,7 @@
                     appendLink(
                       url,
                       relPrefetch,
-                      url.endsWith(".css") ? "style" : "script"
+                      url.endsWith(".css") ? relPreloadStyle : "script"
                     ),
                   true &&
                     !isDependency &&
Diff for index.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +87,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 13.5s 13.6s ⚠️ +133ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-ff68bee..74d0.js gzip N/A 7.32 kB N/A
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-91bc16e..dule.js gzip N/A 6.39 kB N/A
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
_error.js 1.03 MB 1.03 MB ⚠️ +89 B
404.html 4.18 kB 4.22 kB ⚠️ +39 B
hooks.html 3.82 kB 3.86 kB ⚠️ +39 B
index.js 1.03 MB 1.03 MB ⚠️ +89 B
link.js 1.07 MB 1.07 MB ⚠️ +89 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +89 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +89 B
Overall change 5.28 MB 5.28 MB ⚠️ +523 B
Commit: f40d80d

@ijjk
Copy link
Member

ijjk commented Aug 26, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 11.5s 11.8s ⚠️ +306ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Page Load Tests Overall decrease ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
/ failed reqs 0 0
/ total time (seconds) 2.044 2.18 ⚠️ +0.14
/ avg req/sec 1223.02 1146.53 ⚠️ -76.49
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.124 1.119 -0.01
/error-in-render avg req/sec 2224 2233.17 +9.17
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB 7.32 kB -20 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB 6.39 kB -4 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
index.html gzip 948 B 970 B ⚠️ +22 B
link.html gzip 954 B 977 B ⚠️ +23 B
withRouter.html gzip 940 B 963 B ⚠️ +23 B
Overall change 2.84 kB 2.91 kB ⚠️ +68 B

Diffs

Diff for main-209a935..118ae6828.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,14 +319,31 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = function looseToArray(input) {
+        return [].slice.call(input);
+      };
+
       var pageLoader = new _pageLoader["default"](
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(function(e) {
-            return e.getAttribute("href");
+        looseToArray(document.styleSheets)
+          .filter(function(el) {
+            return (
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+            );
+          })
+          .map(function(sheet) {
+            return {
+              href: sheet.ownerNode.getAttribute("href"),
+              text: looseToArray(sheet.cssRules)
+                .map(function(r) {
+                  return r.cssText;
+                })
+                .join("")
+            };
           })
       );
 
@@ -967,8 +984,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise(function(resolve, reject) {
           if (_lastRenderReject) {
             _lastRenderReject();
@@ -979,17 +996,15 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             resolve();
           };
 
-          renderPromiseReject = _lastRenderReject = function lastRenderReject() {
+          _lastRenderReject = function lastRenderReject() {
+            canceled = true;
             _lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -999,82 +1014,30 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(function(el) {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(function(e) {
-              return e.getAttribute("href");
+          var currentHrefs = new Set(
+            currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
             })
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(function(href) {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var _ref9 = (0, _pageLoader.createLink)(href, "stylesheet"),
-                _ref10 = _slicedToArray(_ref9, 2),
-                link = _ref10[0],
-                onload = _ref10[1];
-
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(function(_ref9) {
+            var href = _ref9.href,
+              text = _ref9.text;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required)["catch"](function() {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(function() {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -1083,45 +1046,60 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === _lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(
+              styleSheets.map(function(s) {
+                return s.href;
+              })
             );
-            var relocateHrefs = relocatePlaceholders.map(function(e) {
-              return e.getAttribute("data-n-staging");
-            });
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(function(el) {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
+            );
+            var currentHrefs = currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
+            }); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(function(_ref10) {
+                var href = _ref10.href;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(function(el) {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              function(el) {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -1139,29 +1117,12 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             null,
             /*#__PURE__*/ _react["default"].createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(function() {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === _lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(function() {
-              return (
-                // Wait for rendering to complete:
-                renderPromise
-              );
-            }), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
       function Root(_ref11) {
@@ -1617,12 +1578,9 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _createClass = __webpack_require__("W8MJ");
 
-      var _slicedToArray = __webpack_require__("J4zp");
-
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports["default"] = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1667,6 +1625,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1686,31 +1645,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise(function(res, rej) {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise(function(res, rej) {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var _createLink = createLink(href, rel, as),
-          _createLink2 = _slicedToArray(_createLink, 2),
-          link = _createLink2[0],
-          res = _createLink2[1];
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
 
-        document.head.appendChild(link);
-        return res;
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -2007,7 +1954,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                             // wait for these to resolve. To prevent an unhandled
                             // rejection, we swallow the error which is handled later in
                             // the rendering cycle (this is just a preload optimization).
-                            appendLink(d, relPreload, "style")["catch"](
+                            appendLink(d, relPreload, relPreloadStyle)["catch"](
                               function() {
                                 /* ignore preload error */
                               }
@@ -2064,20 +2011,44 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
+              function fetchStyleSheet(href) {
+                return fetch(href).then(function(res) {
+                  if (!res.ok) throw pageLoadError(href);
+                  return res.text().then(function(text) {
+                    return {
+                      href: href,
+                      text: text
+                    };
+                  });
+                });
+              }
+
               var promisedDeps = // Shared styles will already be on the page:
                 route === "/_app" || false // We use `style-loader` in development:
                   ? Promise.resolve([])
                   : route === this.initialPage
                   ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
                   : // test/integration/css-fixtures/hydrate-without-deps/
-                    this.getDependencies(route);
+                    this.getDependencies(route)
+                      .then(function(deps) {
+                        return deps.filter(function(d) {
+                          return d.endsWith(".css");
+                        });
+                      })
+                      .then(function(cssFiles) {
+                        return (
+                          // These files should've already been fetched by now, so this
+                          // should resolve pretty much instantly.
+                          Promise.all(
+                            cssFiles.map(function(d) {
+                              return fetchStyleSheet(d);
+                            })
+                          )
+                        );
+                      });
               promisedDeps.then(
                 function(deps) {
-                  return register(
-                    deps.filter(function(d) {
-                      return d.endsWith(".css");
-                    })
-                  );
+                  return register(deps);
                 },
                 function(error) {
                   _this5.pageCache[route] = {
@@ -2133,7 +2104,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         appendLink(
                           url,
                           relPrefetch,
-                          url.endsWith(".css") ? "style" : "script"
+                          url.endsWith(".css") ? relPreloadStyle : "script"
                         ),
                       true &&
                         !isDependency &&
Diff for main-be61079..e7.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,13 +239,25 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = input => [].slice.call(input);
+
       var pageLoader = new _pageLoader.default(
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(e => e.getAttribute("href"))
+        looseToArray(document.styleSheets)
+          .filter(
+            el =>
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+          )
+          .map(sheet => ({
+            href: sheet.ownerNode.getAttribute("href"),
+            text: looseToArray(sheet.cssRules)
+              .map(r => r.cssText)
+              .join("")
+          }))
       );
 
       var register = _ref => {
@@ -702,8 +714,8 @@
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise((resolve, reject) => {
           if (lastRenderReject) {
             lastRenderReject();
@@ -714,17 +726,15 @@
             resolve();
           };
 
-          renderPromiseReject = lastRenderReject = () => {
+          lastRenderReject = () => {
+            canceled = true;
             lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -734,79 +744,27 @@
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(el => {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(e => e.getAttribute("href"))
+          var currentHrefs = new Set(
+            currentStyleTags.map(tag => tag.getAttribute("data-n-href"))
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(href => {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var [link, onload] = (0, _pageLoader.createLink)(
-                href,
-                "stylesheet"
-              );
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(_ref7 => {
+            var { href, text } = _ref7;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required).catch(() => {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(() => {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -815,45 +773,56 @@
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(styleSheets.map(s => s.href));
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
             );
-            var relocateHrefs = relocatePlaceholders.map(e =>
-              e.getAttribute("data-n-staging")
-            );
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(el => {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentHrefs = currentStyleTags.map(tag =>
+              tag.getAttribute("data-n-href")
+            ); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(_ref8 => {
+                var { href } = _ref8;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(el => {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              el => {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -871,32 +840,16 @@
             null,
             /*#__PURE__*/ _react.default.createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(() => {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(
-              () =>
-                // Wait for rendering to complete:
-                renderPromise
-            ), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
-      function Root(_ref7) {
-        var { callback, children } = _ref7;
+      function Root(_ref9) {
+        var { callback, children } = _ref9;
 
         // We use `useLayoutEffect` to guarantee the callback is executed
         // as soon as React flushes the update.
@@ -1287,7 +1240,6 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1332,6 +1284,7 @@
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1351,27 +1304,19 @@
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise((res, rej) => {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise((res, rej) => {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var [link, res] = createLink(href, rel, as);
-        document.head.appendChild(link);
-        return res;
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
+
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -1617,7 +1562,7 @@
                         // wait for these to resolve. To prevent an unhandled
                         // rejection, we swallow the error which is handled later in
                         // the rendering cycle (this is just a preload optimization).
-                        appendLink(d, relPreload, "style").catch(() => {
+                        appendLink(d, relPreload, relPreloadStyle).catch(() => {
                           /* ignore preload error */
                         });
                       }
@@ -1666,15 +1611,32 @@
             var check;
           }
 
+          function fetchStyleSheet(href) {
+            return fetch(href).then(res => {
+              if (!res.ok) throw pageLoadError(href);
+              return res.text().then(text => ({
+                href,
+                text
+              }));
+            });
+          }
+
           var promisedDeps = // Shared styles will already be on the page:
             route === "/_app" || false // We use `style-loader` in development:
               ? Promise.resolve([])
               : route === this.initialPage
               ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
               : // test/integration/css-fixtures/hydrate-without-deps/
-                this.getDependencies(route);
+                this.getDependencies(route)
+                  .then(deps => deps.filter(d => d.endsWith(".css")))
+                  .then((
+                    cssFiles // These files should've already been fetched by now, so this
+                  ) =>
+                    // should resolve pretty much instantly.
+                    Promise.all(cssFiles.map(d => fetchStyleSheet(d)))
+                  );
           promisedDeps.then(
-            deps => register(deps.filter(d => d.endsWith(".css"))),
+            deps => register(deps),
             error => {
               this.pageCache[route] = {
                 error
@@ -1722,7 +1684,7 @@
                     appendLink(
                       url,
                       relPrefetch,
-                      url.endsWith(".css") ? "style" : "script"
+                      url.endsWith(".css") ? relPreloadStyle : "script"
                     ),
                   true &&
                     !isDependency &&
Diff for index.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +87,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 13.5s 13.1s -404ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-ff68bee..74d0.js gzip N/A 7.32 kB N/A
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-91bc16e..dule.js gzip N/A 6.39 kB N/A
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
_error.js 1.03 MB 1.03 MB ⚠️ +89 B
404.html 4.18 kB 4.22 kB ⚠️ +39 B
hooks.html 3.82 kB 3.86 kB ⚠️ +39 B
index.js 1.03 MB 1.03 MB ⚠️ +89 B
link.js 1.07 MB 1.07 MB ⚠️ +89 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +89 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +89 B
Overall change 5.28 MB 5.28 MB ⚠️ +523 B
Commit: 393212f

@ijjk
Copy link
Member

ijjk commented Aug 26, 2020

Failing test suites

Commit: 393212f

test/integration/css/test/index.test.js

  • CSS Support > CSS Modules Composes Ordering > Production Mode > should have correct CSS injection order
  • CSS Support > CSS Modules Composes Ordering > Production Mode > should have correct color on index page (on nav from index)
Expand output

● CSS Support › CSS Modules Composes Ordering › Production Mode › should have correct CSS injection order

JavascriptError: javascript error: Cannot read property 'previousSibling' of null
  (Session info: headless chrome=84.0.4147.125)

  1134 |             await checkRedTitle(browser)
  1135 | 
> 1136 |             const newPrevSiblingHref = await browser.eval(
       |                                        ^
  1137 |               `document.querySelector('link[rel=stylesheet][data-n-p]').previousSibling.getAttribute('href')`
  1138 |             )
  1139 |             const newPageHref = await browser.eval(

  at Object.throwDecodedError (../node_modules/selenium-webdriver/lib/error.js:550:15)
  at parseHttpResponse (../node_modules/selenium-webdriver/lib/http.js:565:13)
  at Executor.execute (../node_modules/selenium-webdriver/lib/http.js:491:26)
      at runMicrotasks (<anonymous>)
  at thenableWebDriverProxy.execute (../node_modules/selenium-webdriver/lib/webdriver.js:700:17)
  at Object.<anonymous> (integration/css/test/index.test.js:1136:40)

● CSS Support › CSS Modules Composes Ordering › Production Mode › should have correct color on index page (on nav from index)

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

Expected: "rgb(17, 17, 17)"
Received: "rgb(0, 0, 0)"

  1064 |           `window.getComputedStyle(document.querySelector('#black-title')).color`
  1065 |         )
> 1066 |         expect(titleColor).toBe('rgb(17, 17, 17)')
       |                            ^
  1067 |       }
  1068 |       async function checkRedTitle(browser) {
  1069 |         await browser.waitForElementByCss('#red-title')

  at checkBlackTitle (integration/css/test/index.test.js:1066:28)
      at runMicrotasks (<anonymous>)
  at Object.<anonymous> (integration/css/test/index.test.js:1163:11)

@ijjk
Copy link
Member

ijjk commented Aug 26, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 12.9s 13s ⚠️ +99ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Page Load Tests Overall decrease ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
/ failed reqs 0 0
/ total time (seconds) 2.335 2.449 ⚠️ +0.11
/ avg req/sec 1070.73 1020.92 ⚠️ -49.81
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.257 1.367 ⚠️ +0.11
/error-in-render avg req/sec 1989.55 1829.46 ⚠️ -160.09
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB 7.32 kB -20 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB 6.39 kB -4 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
index.html gzip 948 B 970 B ⚠️ +22 B
link.html gzip 954 B 977 B ⚠️ +23 B
withRouter.html gzip 940 B 963 B ⚠️ +23 B
Overall change 2.84 kB 2.91 kB ⚠️ +68 B

Diffs

Diff for main-209a935..118ae6828.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,14 +319,31 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = function looseToArray(input) {
+        return [].slice.call(input);
+      };
+
       var pageLoader = new _pageLoader["default"](
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(function(e) {
-            return e.getAttribute("href");
+        looseToArray(document.styleSheets)
+          .filter(function(el) {
+            return (
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+            );
+          })
+          .map(function(sheet) {
+            return {
+              href: sheet.ownerNode.getAttribute("href"),
+              text: looseToArray(sheet.cssRules)
+                .map(function(r) {
+                  return r.cssText;
+                })
+                .join("")
+            };
           })
       );
 
@@ -967,8 +984,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise(function(resolve, reject) {
           if (_lastRenderReject) {
             _lastRenderReject();
@@ -979,17 +996,15 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             resolve();
           };
 
-          renderPromiseReject = _lastRenderReject = function lastRenderReject() {
+          _lastRenderReject = function lastRenderReject() {
+            canceled = true;
             _lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -999,82 +1014,30 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(function(el) {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(function(e) {
-              return e.getAttribute("href");
+          var currentHrefs = new Set(
+            currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
             })
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(function(href) {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var _ref9 = (0, _pageLoader.createLink)(href, "stylesheet"),
-                _ref10 = _slicedToArray(_ref9, 2),
-                link = _ref10[0],
-                onload = _ref10[1];
-
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(function(_ref9) {
+            var href = _ref9.href,
+              text = _ref9.text;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required)["catch"](function() {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(function() {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -1083,45 +1046,60 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === _lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(
+              styleSheets.map(function(s) {
+                return s.href;
+              })
             );
-            var relocateHrefs = relocatePlaceholders.map(function(e) {
-              return e.getAttribute("data-n-staging");
-            });
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(function(el) {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
+            );
+            var currentHrefs = currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
+            }); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(function(_ref10) {
+                var href = _ref10.href;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(function(el) {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              function(el) {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -1139,29 +1117,12 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             null,
             /*#__PURE__*/ _react["default"].createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(function() {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === _lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(function() {
-              return (
-                // Wait for rendering to complete:
-                renderPromise
-              );
-            }), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
       function Root(_ref11) {
@@ -1617,12 +1578,9 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _createClass = __webpack_require__("W8MJ");
 
-      var _slicedToArray = __webpack_require__("J4zp");
-
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports["default"] = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1667,6 +1625,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1686,31 +1645,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise(function(res, rej) {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise(function(res, rej) {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var _createLink = createLink(href, rel, as),
-          _createLink2 = _slicedToArray(_createLink, 2),
-          link = _createLink2[0],
-          res = _createLink2[1];
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
 
-        document.head.appendChild(link);
-        return res;
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -2007,7 +1954,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                             // wait for these to resolve. To prevent an unhandled
                             // rejection, we swallow the error which is handled later in
                             // the rendering cycle (this is just a preload optimization).
-                            appendLink(d, relPreload, "style")["catch"](
+                            appendLink(d, relPreload, relPreloadStyle)["catch"](
                               function() {
                                 /* ignore preload error */
                               }
@@ -2064,20 +2011,44 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
+              function fetchStyleSheet(href) {
+                return fetch(href).then(function(res) {
+                  if (!res.ok) throw pageLoadError(href);
+                  return res.text().then(function(text) {
+                    return {
+                      href: href,
+                      text: text
+                    };
+                  });
+                });
+              }
+
               var promisedDeps = // Shared styles will already be on the page:
                 route === "/_app" || false // We use `style-loader` in development:
                   ? Promise.resolve([])
                   : route === this.initialPage
                   ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
                   : // test/integration/css-fixtures/hydrate-without-deps/
-                    this.getDependencies(route);
+                    this.getDependencies(route)
+                      .then(function(deps) {
+                        return deps.filter(function(d) {
+                          return d.endsWith(".css");
+                        });
+                      })
+                      .then(function(cssFiles) {
+                        return (
+                          // These files should've already been fetched by now, so this
+                          // should resolve pretty much instantly.
+                          Promise.all(
+                            cssFiles.map(function(d) {
+                              return fetchStyleSheet(d);
+                            })
+                          )
+                        );
+                      });
               promisedDeps.then(
                 function(deps) {
-                  return register(
-                    deps.filter(function(d) {
-                      return d.endsWith(".css");
-                    })
-                  );
+                  return register(deps);
                 },
                 function(error) {
                   _this5.pageCache[route] = {
@@ -2133,7 +2104,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         appendLink(
                           url,
                           relPrefetch,
-                          url.endsWith(".css") ? "style" : "script"
+                          url.endsWith(".css") ? relPreloadStyle : "script"
                         ),
                       true &&
                         !isDependency &&
Diff for main-be61079..e7.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,13 +239,25 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = input => [].slice.call(input);
+
       var pageLoader = new _pageLoader.default(
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(e => e.getAttribute("href"))
+        looseToArray(document.styleSheets)
+          .filter(
+            el =>
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+          )
+          .map(sheet => ({
+            href: sheet.ownerNode.getAttribute("href"),
+            text: looseToArray(sheet.cssRules)
+              .map(r => r.cssText)
+              .join("")
+          }))
       );
 
       var register = _ref => {
@@ -702,8 +714,8 @@
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise((resolve, reject) => {
           if (lastRenderReject) {
             lastRenderReject();
@@ -714,17 +726,15 @@
             resolve();
           };
 
-          renderPromiseReject = lastRenderReject = () => {
+          lastRenderReject = () => {
+            canceled = true;
             lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -734,79 +744,27 @@
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(el => {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(e => e.getAttribute("href"))
+          var currentHrefs = new Set(
+            currentStyleTags.map(tag => tag.getAttribute("data-n-href"))
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(href => {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var [link, onload] = (0, _pageLoader.createLink)(
-                href,
-                "stylesheet"
-              );
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(_ref7 => {
+            var { href, text } = _ref7;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required).catch(() => {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(() => {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -815,45 +773,56 @@
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(styleSheets.map(s => s.href));
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
             );
-            var relocateHrefs = relocatePlaceholders.map(e =>
-              e.getAttribute("data-n-staging")
-            );
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(el => {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentHrefs = currentStyleTags.map(tag =>
+              tag.getAttribute("data-n-href")
+            ); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(_ref8 => {
+                var { href } = _ref8;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(el => {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              el => {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -871,32 +840,16 @@
             null,
             /*#__PURE__*/ _react.default.createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(() => {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(
-              () =>
-                // Wait for rendering to complete:
-                renderPromise
-            ), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
-      function Root(_ref7) {
-        var { callback, children } = _ref7;
+      function Root(_ref9) {
+        var { callback, children } = _ref9;
 
         // We use `useLayoutEffect` to guarantee the callback is executed
         // as soon as React flushes the update.
@@ -1287,7 +1240,6 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1332,6 +1284,7 @@
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1351,27 +1304,19 @@
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise((res, rej) => {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise((res, rej) => {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var [link, res] = createLink(href, rel, as);
-        document.head.appendChild(link);
-        return res;
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
+
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -1617,7 +1562,7 @@
                         // wait for these to resolve. To prevent an unhandled
                         // rejection, we swallow the error which is handled later in
                         // the rendering cycle (this is just a preload optimization).
-                        appendLink(d, relPreload, "style").catch(() => {
+                        appendLink(d, relPreload, relPreloadStyle).catch(() => {
                           /* ignore preload error */
                         });
                       }
@@ -1666,15 +1611,32 @@
             var check;
           }
 
+          function fetchStyleSheet(href) {
+            return fetch(href).then(res => {
+              if (!res.ok) throw pageLoadError(href);
+              return res.text().then(text => ({
+                href,
+                text
+              }));
+            });
+          }
+
           var promisedDeps = // Shared styles will already be on the page:
             route === "/_app" || false // We use `style-loader` in development:
               ? Promise.resolve([])
               : route === this.initialPage
               ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
               : // test/integration/css-fixtures/hydrate-without-deps/
-                this.getDependencies(route);
+                this.getDependencies(route)
+                  .then(deps => deps.filter(d => d.endsWith(".css")))
+                  .then((
+                    cssFiles // These files should've already been fetched by now, so this
+                  ) =>
+                    // should resolve pretty much instantly.
+                    Promise.all(cssFiles.map(d => fetchStyleSheet(d)))
+                  );
           promisedDeps.then(
-            deps => register(deps.filter(d => d.endsWith(".css"))),
+            deps => register(deps),
             error => {
               this.pageCache[route] = {
                 error
@@ -1722,7 +1684,7 @@
                     appendLink(
                       url,
                       relPrefetch,
-                      url.endsWith(".css") ? "style" : "script"
+                      url.endsWith(".css") ? relPreloadStyle : "script"
                     ),
                   true &&
                     !isDependency &&
Diff for index.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +87,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 14.4s 14.4s ⚠️ +24ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-ff68bee..74d0.js gzip N/A 7.32 kB N/A
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-91bc16e..dule.js gzip N/A 6.39 kB N/A
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
_error.js 1.03 MB 1.03 MB ⚠️ +89 B
404.html 4.18 kB 4.22 kB ⚠️ +39 B
hooks.html 3.82 kB 3.86 kB ⚠️ +39 B
index.js 1.03 MB 1.03 MB ⚠️ +89 B
link.js 1.07 MB 1.07 MB ⚠️ +89 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +89 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +89 B
Overall change 5.28 MB 5.28 MB ⚠️ +523 B
Commit: cef6955

@Timer Timer marked this pull request as ready for review August 26, 2020 15:40
@ijjk
Copy link
Member

ijjk commented Aug 26, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 11.7s 12.6s ⚠️ +968ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Page Load Tests Overall increase ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
/ failed reqs 0 0
/ total time (seconds) 2.242 2.222 -0.02
/ avg req/sec 1115.07 1125.32 +10.25
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.201 1.186 -0.02
/error-in-render avg req/sec 2082.32 2108.46 +26.14
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB 7.32 kB -20 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB 6.39 kB -4 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
index.html gzip 948 B 970 B ⚠️ +22 B
link.html gzip 954 B 977 B ⚠️ +23 B
withRouter.html gzip 940 B 963 B ⚠️ +23 B
Overall change 2.84 kB 2.91 kB ⚠️ +68 B

Diffs

Diff for main-209a935..118ae6828.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,14 +319,31 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = function looseToArray(input) {
+        return [].slice.call(input);
+      };
+
       var pageLoader = new _pageLoader["default"](
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(function(e) {
-            return e.getAttribute("href");
+        looseToArray(document.styleSheets)
+          .filter(function(el) {
+            return (
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+            );
+          })
+          .map(function(sheet) {
+            return {
+              href: sheet.ownerNode.getAttribute("href"),
+              text: looseToArray(sheet.cssRules)
+                .map(function(r) {
+                  return r.cssText;
+                })
+                .join("")
+            };
           })
       );
 
@@ -967,8 +984,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise(function(resolve, reject) {
           if (_lastRenderReject) {
             _lastRenderReject();
@@ -979,17 +996,15 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             resolve();
           };
 
-          renderPromiseReject = _lastRenderReject = function lastRenderReject() {
+          _lastRenderReject = function lastRenderReject() {
+            canceled = true;
             _lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -999,82 +1014,30 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(function(el) {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(function(e) {
-              return e.getAttribute("href");
+          var currentHrefs = new Set(
+            currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
             })
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(function(href) {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var _ref9 = (0, _pageLoader.createLink)(href, "stylesheet"),
-                _ref10 = _slicedToArray(_ref9, 2),
-                link = _ref10[0],
-                onload = _ref10[1];
-
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(function(_ref9) {
+            var href = _ref9.href,
+              text = _ref9.text;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required)["catch"](function() {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(function() {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -1083,45 +1046,60 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === _lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(
+              styleSheets.map(function(s) {
+                return s.href;
+              })
             );
-            var relocateHrefs = relocatePlaceholders.map(function(e) {
-              return e.getAttribute("data-n-staging");
-            });
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(function(el) {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
+            );
+            var currentHrefs = currentStyleTags.map(function(tag) {
+              return tag.getAttribute("data-n-href");
+            }); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(function(_ref10) {
+                var href = _ref10.href;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(function(el) {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              function(el) {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -1139,29 +1117,12 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             null,
             /*#__PURE__*/ _react["default"].createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(function() {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === _lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(function() {
-              return (
-                // Wait for rendering to complete:
-                renderPromise
-              );
-            }), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
       function Root(_ref11) {
@@ -1617,12 +1578,9 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _createClass = __webpack_require__("W8MJ");
 
-      var _slicedToArray = __webpack_require__("J4zp");
-
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports["default"] = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1667,6 +1625,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1686,31 +1645,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise(function(res, rej) {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise(function(res, rej) {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var _createLink = createLink(href, rel, as),
-          _createLink2 = _slicedToArray(_createLink, 2),
-          link = _createLink2[0],
-          res = _createLink2[1];
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
 
-        document.head.appendChild(link);
-        return res;
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -2007,7 +1954,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                             // wait for these to resolve. To prevent an unhandled
                             // rejection, we swallow the error which is handled later in
                             // the rendering cycle (this is just a preload optimization).
-                            appendLink(d, relPreload, "style")["catch"](
+                            appendLink(d, relPreload, relPreloadStyle)["catch"](
                               function() {
                                 /* ignore preload error */
                               }
@@ -2064,20 +2011,44 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
+              function fetchStyleSheet(href) {
+                return fetch(href).then(function(res) {
+                  if (!res.ok) throw pageLoadError(href);
+                  return res.text().then(function(text) {
+                    return {
+                      href: href,
+                      text: text
+                    };
+                  });
+                });
+              }
+
               var promisedDeps = // Shared styles will already be on the page:
                 route === "/_app" || false // We use `style-loader` in development:
                   ? Promise.resolve([])
                   : route === this.initialPage
                   ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
                   : // test/integration/css-fixtures/hydrate-without-deps/
-                    this.getDependencies(route);
+                    this.getDependencies(route)
+                      .then(function(deps) {
+                        return deps.filter(function(d) {
+                          return d.endsWith(".css");
+                        });
+                      })
+                      .then(function(cssFiles) {
+                        return (
+                          // These files should've already been fetched by now, so this
+                          // should resolve pretty much instantly.
+                          Promise.all(
+                            cssFiles.map(function(d) {
+                              return fetchStyleSheet(d);
+                            })
+                          )
+                        );
+                      });
               promisedDeps.then(
                 function(deps) {
-                  return register(
-                    deps.filter(function(d) {
-                      return d.endsWith(".css");
-                    })
-                  );
+                  return register(deps);
                 },
                 function(error) {
                   _this5.pageCache[route] = {
@@ -2133,7 +2104,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         appendLink(
                           url,
                           relPrefetch,
-                          url.endsWith(".css") ? "style" : "script"
+                          url.endsWith(".css") ? relPreloadStyle : "script"
                         ),
                       true &&
                         !isDependency &&
Diff for main-be61079..e7.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,13 +239,25 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
+      var looseToArray = input => [].slice.call(input);
+
       var pageLoader = new _pageLoader.default(
         buildId,
         prefix,
         page,
-        [].slice
-          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
-          .map(e => e.getAttribute("href"))
+        looseToArray(document.styleSheets)
+          .filter(
+            el =>
+              el.ownerNode &&
+              el.ownerNode.tagName === "LINK" &&
+              el.ownerNode.hasAttribute("data-n-p")
+          )
+          .map(sheet => ({
+            href: sheet.ownerNode.getAttribute("href"),
+            text: looseToArray(sheet.cssRules)
+              .map(r => r.cssText)
+              .join("")
+          }))
       );
 
       var register = _ref => {
@@ -702,8 +714,8 @@
         ); // lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
 
         lastAppProps = appProps;
+        var canceled = false;
         var resolvePromise;
-        var renderPromiseReject;
         var renderPromise = new Promise((resolve, reject) => {
           if (lastRenderReject) {
             lastRenderReject();
@@ -714,17 +726,15 @@
             resolve();
           };
 
-          renderPromiseReject = lastRenderReject = () => {
+          lastRenderReject = () => {
+            canceled = true;
             lastRenderReject = null;
             var error = new Error("Cancel rendering route");
             error.cancelled = true;
             reject(error);
           };
-        }); // TODO: consider replacing this with real `<style>` tags that have
-        // plain-text CSS content that's provided by RouteInfo. That'd remove the
-        // need for the staging `<link>`s and the ability for CSS to be missing at
-        // this phase, allowing us to remove the error handling flow that reloads the
-        // page.
+        }); // This function has a return type to ensure it doesn't start returning a
+        // Promise. It should remain synchronous.
 
         function onStart() {
           if (
@@ -734,79 +744,27 @@
             // unless we're in production:
             false
           ) {
-            return Promise.resolve([]);
-          } // Clean up previous render if canceling:
+            return false;
+          }
 
-          [].slice
-            .call(
-              document.querySelectorAll(
-                "link[data-n-staging], noscript[data-n-staging]"
-              )
-            )
-            .forEach(el => {
-              el.parentNode.removeChild(el);
-            });
-          var referenceNodes = [].slice.call(
-            document.querySelectorAll("link[data-n-g], link[data-n-p]")
+          var currentStyleTags = looseToArray(
+            document.querySelectorAll("style[data-n-href]")
           );
-          var referenceHrefs = new Set(
-            referenceNodes.map(e => e.getAttribute("href"))
+          var currentHrefs = new Set(
+            currentStyleTags.map(tag => tag.getAttribute("data-n-href"))
           );
-          var referenceNode = referenceNodes[referenceNodes.length - 1];
-          var required = styleSheets.map(href => {
-            var newNode, promise;
-            var existingLink = referenceHrefs.has(href);
-
-            if (existingLink) {
-              newNode = document.createElement("noscript");
-              newNode.setAttribute("data-n-staging", href);
-              promise = true;
-            } else {
-              var [link, onload] = (0, _pageLoader.createLink)(
-                href,
-                "stylesheet"
-              );
-              link.setAttribute("data-n-staging", ""); // Media `none` does not work in Firefox, so `print` is more
-              // cross-browser. Since this is so short lived we don't have to worry
-              // about style thrashing in a print view (where no routing is going to be
-              // happening anyway).
-
-              link.setAttribute("media", "print");
-              newNode = link;
-              promise = onload;
-            }
-
-            if (referenceNode) {
-              referenceNode.parentNode.insertBefore(
-                newNode,
-                referenceNode.nextSibling
-              );
-              referenceNode = newNode;
-            } else {
-              document.head.appendChild(newNode);
+          styleSheets.forEach(_ref7 => {
+            var { href, text } = _ref7;
+
+            if (!currentHrefs.has(href)) {
+              var styleTag = document.createElement("style");
+              styleTag.setAttribute("data-n-href", href);
+              styleTag.setAttribute("media", "x");
+              document.head.appendChild(styleTag);
+              styleTag.appendChild(document.createTextNode(text));
             }
-
-            return promise;
-          });
-          return Promise.all(required).catch(() => {
-            // This is too late in the rendering lifecycle to use the existing
-            // `PAGE_LOAD_ERROR` flow (via `handleRouteInfoError`).
-            // To match that behavior, we request the page to reload with the current
-            // asPath. This is already set at this phase since we "committed" to the
-            // render.
-            // This handles an edge case where a new deployment is rolled during
-            // client-side transition and the CSS assets are missing.
-            // This prevents:
-            //   1. An unstyled page from being rendered (old behavior)
-            //   2. The `/_error` page being rendered (we want to reload for the new
-            //      deployment)
-            window.location.href = router.asPath; // Instead of rethrowing the CSS loading error, we give a promise that
-            // won't resolve. This pauses the rendering process until the page
-            // reloads. Re-throwing the error could result in a flash of error page.
-            // throw cssLoadingError
-
-            return new Promise(() => {});
           });
+          return true;
         }
 
         function onCommit() {
@@ -815,45 +773,56 @@
             // unless we're in production:
             true && // We can skip this during hydration. Running it wont cause any harm, but
             // we may as well save the CPU cycles:
-            !isInitialRender && // Ensure this render commit owns the currently staged stylesheets:
-            renderPromiseReject === lastRenderReject
+            !isInitialRender && // Ensure this render was not canceled
+            !canceled
           ) {
-            // Remove or relocate old stylesheets:
-            var relocatePlaceholders = [].slice.call(
-              document.querySelectorAll("noscript[data-n-staging]")
+            var desiredHrefs = new Set(styleSheets.map(s => s.href));
+            var currentStyleTags = looseToArray(
+              document.querySelectorAll("style[data-n-href]")
             );
-            var relocateHrefs = relocatePlaceholders.map(e =>
-              e.getAttribute("data-n-staging")
-            );
-            [].slice
-              .call(document.querySelectorAll("link[data-n-p]"))
-              .forEach(el => {
-                var currentHref = el.getAttribute("href");
-                var relocateIndex = relocateHrefs.indexOf(currentHref);
-
-                if (relocateIndex !== -1) {
-                  var _placeholderElement$p;
-
-                  var placeholderElement = relocatePlaceholders[relocateIndex];
-                  (_placeholderElement$p = placeholderElement.parentNode) ==
-                  null
-                    ? void 0
-                    : _placeholderElement$p.replaceChild(
-                        el,
-                        placeholderElement
-                      );
-                } else {
-                  el.parentNode.removeChild(el);
+            var currentHrefs = currentStyleTags.map(tag =>
+              tag.getAttribute("data-n-href")
+            ); // Toggle `<style>` tags on or off depending on if they're needed:
+
+            for (var idx = 0; idx < currentHrefs.length; ++idx) {
+              if (desiredHrefs.has(currentHrefs[idx])) {
+                currentStyleTags[idx].removeAttribute("media");
+              } else {
+                currentStyleTags[idx].setAttribute("media", "x");
+              }
+            } // Reorder styles into intended order:
+
+            var referenceNode = document.querySelector("noscript[data-n-css]");
+
+            if (
+              // This should be an invariant:
+              referenceNode
+            ) {
+              styleSheets.forEach(_ref8 => {
+                var { href } = _ref8;
+                var targetTag = document.querySelector(
+                  'style[data-n-href="'.concat(href, '"]')
+                );
+
+                if (
+                  // This should be an invariant:
+                  targetTag
+                ) {
+                  referenceNode.parentNode.insertBefore(
+                    targetTag,
+                    referenceNode.nextSibling
+                  );
+                  referenceNode = targetTag;
                 }
-              }); // Activate new stylesheets:
-            [].slice
-              .call(document.querySelectorAll("link[data-n-staging]"))
-              .forEach(el => {
-                el.removeAttribute("data-n-staging");
-                el.removeAttribute("media");
-                el.setAttribute("data-n-p", "");
-              }); // Force browser to recompute layout, which prevents a flash of unstyled
-            // content:
+              });
+            } // Finally, clean up server rendered stylesheets:
+
+            looseToArray(document.querySelectorAll("link[data-n-p]")).forEach(
+              el => {
+                el.parentNode.removeChild(el);
+              }
+            ); // Force browser to recompute layout, which should prevent a flash of
+            // unstyled content:
 
             getComputedStyle(document.body, "height");
           }
@@ -871,32 +840,16 @@
             null,
             /*#__PURE__*/ _react.default.createElement(App, appProps)
           )
-        ); // We catch runtime errors using componentDidCatch which will trigger renderError
-
-        return Promise.race([
-          // Download required CSS assets first:
-          onStart()
-            .then(() => {
-              // Ensure a new render has not been started:
-              if (renderPromiseReject === lastRenderReject) {
-                // Queue rendering:
-                renderReactElement(
-                  false ? /*#__PURE__*/ undefined : elem,
-                  appElement
-                );
-              }
-            })
-            .then(
-              () =>
-                // Wait for rendering to complete:
-                renderPromise
-            ), // Bail early on route cancelation (rejection):
-          renderPromise
-        ]);
+        );
+
+        onStart(); // We catch runtime errors using componentDidCatch which will trigger renderError
+
+        renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
+        return renderPromise;
       }
 
-      function Root(_ref7) {
-        var { callback, children } = _ref7;
+      function Root(_ref9) {
+        var { callback, children } = _ref9;
 
         // We use `useLayoutEffect` to guarantee the callback is executed
         // as soon as React flushes the update.
@@ -1287,7 +1240,6 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
-      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1332,6 +1284,7 @@
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
       var relPreload = hasRel("preload") ? "preload" : relPrefetch;
+      var relPreloadStyle = "fetch";
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1351,27 +1304,19 @@
         return route.replace(/\/$/, "");
       }
 
-      function createLink(href, rel, as, link) {
-        link = document.createElement("link");
-        return [
-          link,
-          new Promise((res, rej) => {
-            // The order of property assignment here is intentional:
-            if (as) link.as = as;
-            link.rel = rel;
-            link.crossOrigin = "anonymous";
-            link.onload = res;
-            link.onerror = rej; // `href` should always be last:
-
-            link.href = href;
-          })
-        ];
-      }
+      function appendLink(href, rel, as, link) {
+        return new Promise((res, rej) => {
+          link = document.createElement("link"); // The order of property assignment here is intentional:
 
-      function appendLink(href, rel, as) {
-        var [link, res] = createLink(href, rel, as);
-        document.head.appendChild(link);
-        return res;
+          if (as) link.as = as;
+          link.rel = rel;
+          link.crossOrigin = "anonymous";
+          link.onload = res;
+          link.onerror = rej; // `href` should always be last:
+
+          link.href = href;
+          document.head.appendChild(link);
+        });
       }
 
       function loadScript(url) {
@@ -1617,7 +1562,7 @@
                         // wait for these to resolve. To prevent an unhandled
                         // rejection, we swallow the error which is handled later in
                         // the rendering cycle (this is just a preload optimization).
-                        appendLink(d, relPreload, "style").catch(() => {
+                        appendLink(d, relPreload, relPreloadStyle).catch(() => {
                           /* ignore preload error */
                         });
                       }
@@ -1666,15 +1611,32 @@
             var check;
           }
 
+          function fetchStyleSheet(href) {
+            return fetch(href).then(res => {
+              if (!res.ok) throw pageLoadError(href);
+              return res.text().then(text => ({
+                href,
+                text
+              }));
+            });
+          }
+
           var promisedDeps = // Shared styles will already be on the page:
             route === "/_app" || false // We use `style-loader` in development:
               ? Promise.resolve([])
               : route === this.initialPage
               ? Promise.resolve(this.initialStyleSheets) // Tests that this does not block hydration:
               : // test/integration/css-fixtures/hydrate-without-deps/
-                this.getDependencies(route);
+                this.getDependencies(route)
+                  .then(deps => deps.filter(d => d.endsWith(".css")))
+                  .then((
+                    cssFiles // These files should've already been fetched by now, so this
+                  ) =>
+                    // should resolve pretty much instantly.
+                    Promise.all(cssFiles.map(d => fetchStyleSheet(d)))
+                  );
           promisedDeps.then(
-            deps => register(deps.filter(d => d.endsWith(".css"))),
+            deps => register(deps),
             error => {
               this.pageCache[route] = {
                 error
@@ -1722,7 +1684,7 @@
                     appendLink(
                       url,
                       relPrefetch,
-                      url.endsWith(".css") ? "style" : "script"
+                      url.endsWith(".css") ? relPreloadStyle : "script"
                     ),
                   true &&
                     !isDependency &&
Diff for index.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +87,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -4,9 +4,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
     <meta name="next-head-count" content="2" />
+    <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      href="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +82,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-209a935a087118ae6828.js"
+      src="/_next/static/chunks/main-fe24fe70b35ca4db2791.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-be610799d99109ebb1e7.module.js"
+      src="/_next/static/chunks/main-8203add16da23ec45de4.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
buildDuration 13.7s 13.6s -94ms
nodeModulesSize 57.5 MB 57.5 MB -3.18 kB
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..7f47.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-d4bab31..824e.js gzip 7.34 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-ff68bee..74d0.js gzip N/A 7.32 kB N/A
Overall change 57.4 kB 57.3 kB -20 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary Timer/next.js wip/style-tags Change
677f882d2ed8..dule.js gzip 6.15 kB 6.15 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-438d30a..dule.js gzip 6.39 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-91bc16e..dule.js gzip N/A 6.39 kB N/A
Overall change 52.3 kB 52.3 kB -4 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js wip/style-tags Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-28298..e0c9.js gzip 3.44 kB 3.44 kB
hooks-89731c..c609.js gzip 887 B 887 B
index-17468f..5d83.js gzip 227 B 227 B
link-000f151..65d4.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary Timer/next.js wip/style-tags Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-65c8a..dule.js gzip 2.29 kB 2.29 kB
hooks-cbf13f..dule.js gzip 387 B 387 B
index-b9a643..dule.js gzip 226 B 226 B
link-4cfda7a..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary Timer/next.js wip/style-tags Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 330 B 330 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary Timer/next.js wip/style-tags Change
_error.js 1.03 MB 1.03 MB ⚠️ +89 B
404.html 4.18 kB 4.22 kB ⚠️ +39 B
hooks.html 3.82 kB 3.86 kB ⚠️ +39 B
index.js 1.03 MB 1.03 MB ⚠️ +89 B
link.js 1.07 MB 1.07 MB ⚠️ +89 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +89 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +89 B
Overall change 5.28 MB 5.28 MB ⚠️ +523 B
Commit: 37cdc9c

@kodiakhq kodiakhq bot merged commit 6895f9b into vercel:canary Aug 26, 2020
@Timer Timer deleted the wip/style-tags branch August 26, 2020 16:35
@dkempner
Copy link

dkempner commented Sep 12, 2020

@Timer i think we're seeing some CORS issues loading external CSS because of this change on next 9.5.3

We use the assetPrefix option to load our CSS from our CDN but after this change the fetch is throwing (even though we allow CORS * on our CDN)

specifically i think this is the culprit:
https://github.com/vercel/next.js/pull/16581/files#diff-4666274c1f38a74ebd0b4c1b834e240aR36

@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace <link rel=stylesheet> client-side transitions with <style> tags

3 participants