Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Update stylesheets on page navigation #16126

Merged
merged 23 commits into from
Aug 17, 2020

Conversation

Timer
Copy link
Member

@Timer Timer commented Aug 12, 2020

This pull request adds a test case for the reproduction provided in #12445. This bug is specifically caused when loading the next page before navigation has actually occurred.


Fixes #12445

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk

This comment has been minimized.

@ijjk
Copy link
Member

ijjk commented Aug 17, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 10.8s 11s ⚠️ +210ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +12.5 kB
Page Load Tests Overall increase ✓
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
/ failed reqs 0 0
/ total time (seconds) 1.909 1.907 0
/ avg req/sec 1309.28 1311.15 +1.87
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.063 1.059 0
/error-in-render avg req/sec 2351.16 2359.99 +8.83
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB 10.2 kB ⚠️ +32 B
framework.HASH.js gzip 39 kB 39 kB
main-8c9d3f7..4367.js gzip 6.72 kB 7.15 kB ⚠️ +424 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 56.7 kB 57.1 kB ⚠️ +456 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB 6.11 kB ⚠️ +30 B
framework.HA..dule.js gzip 39 kB 39 kB
main-e89c24f..dule.js gzip 5.79 kB 6.16 kB ⚠️ +369 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 51.6 kB 52 kB ⚠️ +399 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 decrease ✓
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
index.html gzip 948 B 946 B -2 B
link.html gzip 953 B 951 B -2 B
withRouter.html gzip 941 B 938 B -3 B
Overall change 2.84 kB 2.83 kB -7 B

Diffs

Diff for 677f882d2ed8..5d.module.js
@@ -728,6 +728,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App,
             wrapApp,
             Component,
+            initialStyleSheets,
             err,
             subscription,
             isFallback
@@ -809,6 +810,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -817,7 +819,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1114,9 +1119,12 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           try {
-            var { page: Component } = await this.fetchComponent("/_error");
+            var { page: Component, styleSheets } = await this.fetchComponent(
+              "/_error"
+            );
             var routeInfo = {
               Component,
+              styleSheets,
               err,
               error: err
             };
@@ -1161,6 +1169,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
               ? cachedRouteInfo
               : await this.fetchComponent(route).then(res => ({
                   Component: res.page,
+                  styleSheets: res.styleSheets,
                   __N_SSG: res.mod.__N_SSG,
                   __N_SSP: res.mod.__N_SSP
                 }));
Diff for 677f882d2ed8..6e2c5bf9e.js
@@ -872,6 +872,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App = _ref.App,
             wrapApp = _ref.wrapApp,
             Component = _ref.Component,
+            initialStyleSheets = _ref.initialStyleSheets,
             err = _ref.err,
             subscription = _ref.subscription,
             isFallback = _ref.isFallback;
@@ -962,6 +963,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component: Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err: err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -970,7 +972,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1408,7 +1413,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                   as,
                   loadErrorFail
                 ) {
-                  var _yield$this$fetchComp, Component, routeInfo;
+                  var _yield$this$fetchComp, Component, styleSheets, routeInfo;
 
                   return _regeneratorRuntime.wrap(
                     function _callee2$(_context2) {
@@ -1447,38 +1452,40 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                           case 9:
                             _yield$this$fetchComp = _context2.sent;
                             Component = _yield$this$fetchComp.page;
+                            styleSheets = _yield$this$fetchComp.styleSheets;
                             routeInfo = {
                               Component: Component,
+                              styleSheets: styleSheets,
                               err: err,
                               error: err
                             };
-                            _context2.prev = 12;
-                            _context2.next = 15;
+                            _context2.prev = 13;
+                            _context2.next = 16;
                             return this.getInitialProps(Component, {
                               err: err,
                               pathname: pathname,
                               query: query
                             });
 
-                          case 15:
+                          case 16:
                             routeInfo.props = _context2.sent;
-                            _context2.next = 22;
+                            _context2.next = 23;
                             break;
 
-                          case 18:
-                            _context2.prev = 18;
-                            _context2.t0 = _context2["catch"](12);
+                          case 19:
+                            _context2.prev = 19;
+                            _context2.t0 = _context2["catch"](13);
                             console.error(
                               "Error in error page `getInitialProps`: ",
                               _context2.t0
                             );
                             routeInfo.props = {};
 
-                          case 22:
+                          case 23:
                             return _context2.abrupt("return", routeInfo);
 
-                          case 25:
-                            _context2.prev = 25;
+                          case 26:
+                            _context2.prev = 26;
                             _context2.t1 = _context2["catch"](6);
                             return _context2.abrupt(
                               "return",
@@ -1491,7 +1498,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                               )
                             );
 
-                          case 28:
+                          case 29:
                           case "end":
                             return _context2.stop();
                         }
@@ -1500,8 +1507,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                     _callee2,
                     this,
                     [
-                      [6, 25],
-                      [12, 18]
+                      [6, 26],
+                      [13, 19]
                     ]
                   );
                 })
@@ -1580,6 +1587,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                             ) {
                               return {
                                 Component: res.page,
+                                styleSheets: res.styleSheets,
                                 __N_SSG: res.mod.__N_SSG,
                                 __N_SSP: res.mod.__N_SSP
                               };
Diff for main-7635c55..431c7b079.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,7 +319,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader["default"](buildId, prefix, page);
+      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");
+          })
+      );
 
       var register = function register(_ref) {
         var _ref2 = _slicedToArray(_ref, 2),
@@ -351,6 +360,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       var Container = /*#__PURE__*/ (function(_react$default$Compon) {
@@ -543,9 +553,10 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                     case 14:
                       _yield$pageLoader$loa2 = _context.sent;
                       CachedComponent = _yield$pageLoader$loa2.page;
+                      cachedStyleSheets = _yield$pageLoader$loa2.styleSheets;
 
                       if (true) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -559,7 +570,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         (isValidElementType = _require2.isValidElementType);
 
                       if (isValidElementType(CachedComponent)) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -570,29 +581,29 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         )
                       );
 
-                    case 20:
-                      _context.next = 25;
+                    case 21:
+                      _context.next = 26;
                       break;
 
-                    case 22:
-                      _context.prev = 22;
+                    case 23:
+                      _context.prev = 23;
                       _context.t0 = _context["catch"](10);
                       // This catches errors like throwing in the top level of a module
                       initialErr = _context.t0;
 
-                    case 25:
+                    case 26:
                       if (false) {
                       }
 
                       if (!window.__NEXT_PRELOADREADY) {
-                        _context.next = 29;
+                        _context.next = 30;
                         break;
                       }
 
-                      _context.next = 29;
+                      _context.next = 30;
                       return window.__NEXT_PRELOADREADY(dynamicIds);
 
-                    case 29:
+                    case 30:
                       exports.router = router = (0, _router2.createRouter)(
                         page,
                         query,
@@ -602,16 +613,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           pageLoader: pageLoader,
                           App: CachedApp,
                           Component: CachedComponent,
+                          initialStyleSheets: cachedStyleSheets,
                           wrapApp: wrapApp,
                           err: initialErr,
                           isFallback: Boolean(isFallback),
                           subscription: function subscription(_ref5, App) {
                             var Component = _ref5.Component,
+                              styleSheets = _ref5.styleSheets,
                               props = _ref5.props,
                               err = _ref5.err;
                             return render({
                               App: App,
                               Component: Component,
+                              styleSheets: styleSheets,
                               props: props,
                               err: err
                             });
@@ -625,6 +639,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       renderCtx = {
                         App: CachedApp,
                         Component: CachedComponent,
+                        styleSheets: cachedStyleSheets,
                         props: hydrateProps,
                         err: initialErr
                       };
@@ -635,14 +650,14 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       render(renderCtx);
                       return _context.abrupt("return", emitter);
 
-                    case 37:
+                    case 38:
                       return _context.abrupt("return", {
                         emitter: emitter,
                         render: render,
                         renderCtx: renderCtx
                       });
 
-                    case 38:
+                    case 39:
                     case "end":
                       return _context.stop();
                   }
@@ -650,7 +665,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               },
               _callee,
               null,
-              [[10, 22]]
+              [[10, 23]]
             );
           })
         );
@@ -744,7 +759,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(function(_ref6) {
-          var ErrorComponent = _ref6.page;
+          var ErrorComponent = _ref6.page,
+            styleSheets = _ref6.styleSheets;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -773,6 +789,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 {
                   err: err,
                   Component: ErrorComponent,
+                  styleSheets: styleSheets,
                   props: initProps
                 }
               )
@@ -934,18 +951,105 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               Component,
               props,
               err,
+              styleSheets,
               appProps,
               resolvePromise,
               renderPromise,
+              onStart,
+              onAbort,
+              onCommit,
               elem;
             return _regeneratorRuntime.wrap(function _callee3$(_context3) {
               while (1) {
                 switch ((_context3.prev = _context3.next)) {
                   case 0:
+                    onCommit = function _onCommit() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        true
+                      ) {
+                        // Remove old stylesheets:
+                        document
+                          .querySelectorAll("link[data-n-p]")
+                          .forEach(function(el) {
+                            return el.remove();
+                          }); // 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", "");
+                          });
+                      }
+
+                      resolvePromise();
+                    };
+
+                    onAbort = function _onAbort() {
+                      // TODO: test this cleanup on a render abort
+                      document
+                        .querySelectorAll("link[data-n-staging]")
+                        .forEach(function(el) {
+                          el.remove();
+                        });
+                    };
+
+                    onStart = function _onStart() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        false
+                      ) {
+                        return Promise.resolve([]);
+                      } // TODO: test injection position
+
+                      var referenceNode = [].slice
+                        .call(
+                          document.querySelectorAll(
+                            "link[data-n-g], link[data-n-p]"
+                          )
+                        )
+                        .pop();
+                      var required = styleSheets.map(function(href) {
+                        var _ref10 = (0, _pageLoader.createLink)(
+                            href,
+                            "stylesheet"
+                          ),
+                          _ref11 = _slicedToArray(_ref10, 2),
+                          link = _ref11[0],
+                          promise = _ref11[1];
+
+                        link.setAttribute("data-n-staging", "");
+                        link.setAttribute("media", "none");
+
+                        if (referenceNode) {
+                          referenceNode.parentNode.insertBefore(
+                            link,
+                            referenceNode.nextSibling
+                          );
+                          referenceNode = link;
+                        } else {
+                          document.head.appendChild(link);
+                        }
+
+                        return promise;
+                      });
+                      return Promise.all(required);
+                    };
+
                     (App = _ref8.App),
                       (Component = _ref8.Component),
                       (props = _ref8.props),
-                      (err = _ref8.err);
+                      (err = _ref8.err),
+                      (styleSheets = _ref8.styleSheets);
                     Component = Component || lastAppProps.Component;
                     props = props || lastAppProps.props;
                     appProps = (0, _extends2["default"])(
@@ -974,10 +1078,14 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         reject();
                       };
                     });
+                    renderPromise["catch"](function(abortError) {
+                      onAbort();
+                      throw abortError;
+                    });
                     elem = /*#__PURE__*/ _react["default"].createElement(
                       Root,
                       {
-                        callback: resolvePromise
+                        callback: onCommit
                       },
                       /*#__PURE__*/ _react["default"].createElement(
                         AppContainer,
@@ -989,14 +1097,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       )
                     ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+                    _context3.next = 13;
+                    return onStart();
+
+                  case 13:
+                    // TODO: test CSS loading error
                     renderReactElement(
                       false ? /*#__PURE__*/ undefined : elem,
                       appElement
                     );
-                    _context3.next = 10;
+                    _context3.next = 16;
                     return renderPromise;
 
-                  case 10:
+                  case 16:
                   case "end":
                     return _context3.stop();
                 }
@@ -1460,9 +1573,12 @@ _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"));
@@ -1506,6 +1622,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1525,23 +1642,42 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise(function(res, rej) {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise(function(res, rej) {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var _createLink = createLink(href, rel, as),
+          _createLink2 = _slicedToArray(_createLink, 2),
+          link = _createLink2[0],
+          res = _createLink2[1];
+
+        document.head.appendChild(link);
+        return res;
       }
 
       var PageLoader = /*#__PURE__*/ (function() {
-        function PageLoader(buildId, assetPrefix, initialPage) {
+        function PageLoader(
+          buildId,
+          assetPrefix,
+          initialPage,
+          initialStyleSheets
+        ) {
           _classCallCheck(this, PageLoader);
 
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1550,6 +1686,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1785,18 +1923,25 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           )
                         ) {
                           _this4.loadScript(d, route);
-                        }
+                        } // Prefetch CSS as it'll be needed when the page JavaScript
+                        // evaluates. This will only trigger if explicit prefetching is
+                        // disabled for a <Link>... prefetching in this case is desirable
+                        // because we *know* it's going to be used very soon (page was
+                        // loaded).
 
                         if (
                           d.endsWith(".css") &&
                           !document.querySelector(
-                            'link[rel=stylesheet][href^="'.concat(d, '"]')
+                            'link[rel="'
+                              .concat(relPreload, '"][href^="')
+                              .concat(d, '"]')
                           )
                         ) {
-                          appendLink(d, "stylesheet")["catch"](function() {
-                            // FIXME: handle failure
-                            // Right now, this is needed to prevent an unhandled rejection.
-                          });
+                          appendLink(d, relPreload, "style")["catch"](
+                            function() {
+                              /* ignore preload error */
+                            }
+                          );
                         }
                       });
                     });
@@ -1835,12 +1980,13 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             value: function registerPage(route, regFn) {
               var _this6 = this;
 
-              var register = function register() {
+              var register = function register(styleSheets) {
                 try {
                   var mod = regFn();
                   var pageData = {
                     page: mod["default"] || mod,
-                    mod: mod
+                    mod: mod,
+                    styleSheets: styleSheets
                   };
                   _this6.pageCache[route] = pageData;
 
@@ -1860,7 +2006,30 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
-              register();
+              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) // TODO: test this doesn't block register of initial page
+                  : this.getDependencies(route);
+              promisedDeps.then(
+                function(deps) {
+                  return register(
+                    deps.filter(function(d) {
+                      return d.endsWith(".css");
+                    })
+                  );
+                },
+                function(error) {
+                  _this6.pageCache[route] = {
+                    error: error
+                  };
+
+                  _this6.pageRegisterEvents.emit(route, {
+                    error: error
+                  });
+                }
+              );
             }
             /**
              * @param {string} route
Diff for main-a4b7a58..bc.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,7 +239,14 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader.default(buildId, prefix, page);
+      var pageLoader = new _pageLoader.default(
+        buildId,
+        prefix,
+        page,
+        [].slice
+          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
+          .map(e => e.getAttribute("href"))
+      );
 
       var register = _ref => {
         var [r, f] = _ref;
@@ -262,6 +269,7 @@
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       class Container extends _react.default.Component {
@@ -395,7 +403,10 @@
         var initialErr = hydrateErr;
 
         try {
-          ({ page: CachedComponent } = await pageLoader.loadPage(page));
+          ({
+            page: CachedComponent,
+            styleSheets: cachedStyleSheets
+          } = await pageLoader.loadPage(page));
 
           if (false) {
             var isValidElementType;
@@ -422,14 +433,16 @@
             pageLoader,
             App: CachedApp,
             Component: CachedComponent,
+            initialStyleSheets: cachedStyleSheets,
             wrapApp,
             err: initialErr,
             isFallback: Boolean(isFallback),
             subscription: (_ref3, App) => {
-              var { Component, props, err } = _ref3;
+              var { Component, styleSheets, props, err } = _ref3;
               return render({
                 App,
                 Component,
+                styleSheets,
                 props,
                 err
               });
@@ -443,6 +456,7 @@
         var renderCtx = {
           App: CachedApp,
           Component: CachedComponent,
+          styleSheets: cachedStyleSheets,
           props: hydrateProps,
           err: initialErr
         };
@@ -494,7 +508,7 @@
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(_ref4 => {
-          var { page: ErrorComponent } = _ref4;
+          var { page: ErrorComponent, styleSheets } = _ref4;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -523,6 +537,7 @@
                 {
                   err,
                   Component: ErrorComponent,
+                  styleSheets,
                   props: initProps
                 }
               )
@@ -668,7 +683,7 @@
       };
 
       async function doRender(_ref6) {
-        var { App, Component, props, err } = _ref6;
+        var { App, Component, props, err, styleSheets } = _ref6;
         Component = Component || lastAppProps.Component;
         props = props || lastAppProps.props;
         var appProps = (0, _extends2.default)(
@@ -699,10 +714,83 @@
           };
         });
 
+        function onStart() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            false
+          ) {
+            return Promise.resolve([]);
+          } // TODO: test injection position
+
+          var referenceNode = [].slice
+            .call(document.querySelectorAll("link[data-n-g], link[data-n-p]"))
+            .pop();
+          var required = styleSheets.map(href => {
+            var [link, promise] = (0, _pageLoader.createLink)(
+              href,
+              "stylesheet"
+            );
+            link.setAttribute("data-n-staging", "");
+            link.setAttribute("media", "none");
+
+            if (referenceNode) {
+              referenceNode.parentNode.insertBefore(
+                link,
+                referenceNode.nextSibling
+              );
+              referenceNode = link;
+            } else {
+              document.head.appendChild(link);
+            }
+
+            return promise;
+          });
+          return Promise.all(required);
+        }
+
+        function onAbort() {
+          // TODO: test this cleanup on a render abort
+          document.querySelectorAll("link[data-n-staging]").forEach(el => {
+            el.remove();
+          });
+        }
+
+        renderPromise.catch(abortError => {
+          onAbort();
+          throw abortError;
+        });
+
+        function onCommit() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            true
+          ) {
+            // Remove old stylesheets:
+            document
+              .querySelectorAll("link[data-n-p]")
+              .forEach(el => el.remove()); // 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", "");
+              });
+          }
+
+          resolvePromise();
+        }
+
         var elem = /*#__PURE__*/ _react.default.createElement(
           Root,
           {
-            callback: resolvePromise
+            callback: onCommit
           },
           /*#__PURE__*/ _react.default.createElement(
             AppContainer,
@@ -711,6 +799,8 @@
           )
         ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+        await onStart(); // TODO: test CSS loading error
+
         renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
         await renderPromise;
       }
@@ -1107,6 +1197,7 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
+      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1150,6 +1241,7 @@
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1169,21 +1261,31 @@
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise((res, rej) => {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise((res, rej) => {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var [link, res] = createLink(href, rel, as);
+        document.head.appendChild(link);
+        return res;
       }
 
       class PageLoader {
-        constructor(buildId, assetPrefix, initialPage) {
+        constructor(buildId, assetPrefix, initialPage, initialStyleSheets) {
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1192,6 +1294,8 @@
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1386,17 +1490,22 @@
                       !document.querySelector('script[src^="'.concat(d, '"]'))
                     ) {
                       this.loadScript(d, route);
-                    }
+                    } // Prefetch CSS as it'll be needed when the page JavaScript
+                    // evaluates. This will only trigger if explicit prefetching is
+                    // disabled for a <Link>... prefetching in this case is desirable
+                    // because we *know* it's going to be used very soon (page was
+                    // loaded).
 
                     if (
                       d.endsWith(".css") &&
                       !document.querySelector(
-                        'link[rel=stylesheet][href^="'.concat(d, '"]')
+                        'link[rel="'
+                          .concat(relPreload, '"][href^="')
+                          .concat(d, '"]')
                       )
                     ) {
-                      appendLink(d, "stylesheet").catch(() => {
-                        // FIXME: handle failure
-                        // Right now, this is needed to prevent an unhandled rejection.
+                      appendLink(d, relPreload, "style").catch(() => {
+                        /* ignore preload error */
                       });
                     }
                   });
@@ -1428,12 +1537,13 @@
         } // This method if called by the route code.
 
         registerPage(route, regFn) {
-          var register = () => {
+          var register = styleSheets => {
             try {
               var mod = regFn();
               var pageData = {
                 page: mod.default || mod,
-                mod
+                mod,
+                styleSheets
               };
               this.pageCache[route] = pageData;
               this.pageRegisterEvents.emit(route, pageData);
@@ -1451,7 +1561,23 @@
             var check;
           }
 
-          register();
+          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) // TODO: test this doesn't block register of initial page
+              : this.getDependencies(route);
+          promisedDeps.then(
+            deps => register(deps.filter(d => d.endsWith(".css"))),
+            error => {
+              this.pageCache[route] = {
+                error
+              };
+              this.pageRegisterEvents.emit(route, {
+                error
+              });
+            }
+          );
         }
         /**
          * @param {string} route
Diff for index.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      href="/_next/static/chunks/main-9c4b0fe19364df146c4c.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7635c556f5f431c7b079.js"
+      src="/_next/static/chunks/main-33884615e2f4eea57426.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      src="/_next/static/chunks/main-9c4b0fe19364df146c4c.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      href="/_next/static/chunks/main-9c4b0fe19364df146c4c.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +86,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7635c556f5f431c7b079.js"
+      src="/_next/static/chunks/main-33884615e2f4eea57426.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      src="/_next/static/chunks/main-9c4b0fe19364df146c4c.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -122,13 +122,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      href="/_next/static/chunks/main-9c4b0fe19364df146c4c.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7635c556f5f431c7b079.js"
+      src="/_next/static/chunks/main-33884615e2f4eea57426.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      src="/_next/static/chunks/main-9c4b0fe19364df146c4c.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 12.2s 12.6s ⚠️ +313ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +12.5 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB N/A N/A
framework.HASH.js gzip 39 kB 39 kB
main-8c9d3f7..4367.js gzip 6.72 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
677f882d2ed8..aa9e.js gzip N/A 10.2 kB N/A
main-deb7f46..ed9f.js gzip N/A 7.15 kB N/A
Overall change 56.7 kB 57.1 kB ⚠️ +456 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB N/A N/A
framework.HA..dule.js gzip 39 kB 39 kB
main-e89c24f..dule.js gzip 5.79 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
677f882d2ed8..dule.js gzip N/A 6.11 kB N/A
main-4665366..dule.js gzip N/A 6.16 kB N/A
Overall change 51.6 kB 52 kB ⚠️ +399 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_error.js 1.03 MB 1.03 MB ⚠️ +204 B
404.html 4.18 kB 4.18 kB
hooks.html 3.82 kB 3.82 kB
index.js 1.03 MB 1.03 MB ⚠️ +204 B
link.js 1.07 MB 1.07 MB ⚠️ +437 B
routerDirect.js 1.06 MB 1.07 MB ⚠️ +437 B
withRouter.js 1.06 MB 1.07 MB ⚠️ +437 B
Overall change 5.27 MB 5.27 MB ⚠️ +1.72 kB
Commit: f20cb83

prateekbh
prateekbh previously approved these changes Aug 17, 2020
@ijjk
Copy link
Member

ijjk commented Aug 17, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 14s 14.4s ⚠️ +345ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +12.4 kB
Page Load Tests Overall decrease ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
/ failed reqs 0 0
/ total time (seconds) 2.636 2.669 ⚠️ +0.03
/ avg req/sec 948.47 936.6 ⚠️ -11.87
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.603 1.63 ⚠️ +0.03
/error-in-render avg req/sec 1559.88 1533.75 ⚠️ -26.13
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB 10.2 kB ⚠️ +32 B
framework.HASH.js gzip 39 kB 39 kB
main-8c9d3f7..4367.js gzip 6.72 kB 7.15 kB ⚠️ +424 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 56.7 kB 57.1 kB ⚠️ +456 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB 6.11 kB ⚠️ +30 B
framework.HA..dule.js gzip 39 kB 39 kB
main-e89c24f..dule.js gzip 5.79 kB 6.16 kB ⚠️ +369 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 51.6 kB 52 kB ⚠️ +399 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 decrease ✓
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
index.html gzip 948 B 947 B -1 B
link.html gzip 953 B 952 B -1 B
withRouter.html gzip 941 B 938 B -3 B
Overall change 2.84 kB 2.84 kB -5 B

Diffs

Diff for 677f882d2ed8..5d.module.js
@@ -728,6 +728,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App,
             wrapApp,
             Component,
+            initialStyleSheets,
             err,
             subscription,
             isFallback
@@ -809,6 +810,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -817,7 +819,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1114,9 +1119,12 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           try {
-            var { page: Component } = await this.fetchComponent("/_error");
+            var { page: Component, styleSheets } = await this.fetchComponent(
+              "/_error"
+            );
             var routeInfo = {
               Component,
+              styleSheets,
               err,
               error: err
             };
@@ -1161,6 +1169,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
               ? cachedRouteInfo
               : await this.fetchComponent(route).then(res => ({
                   Component: res.page,
+                  styleSheets: res.styleSheets,
                   __N_SSG: res.mod.__N_SSG,
                   __N_SSP: res.mod.__N_SSP
                 }));
Diff for 677f882d2ed8..6e2c5bf9e.js
@@ -872,6 +872,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App = _ref.App,
             wrapApp = _ref.wrapApp,
             Component = _ref.Component,
+            initialStyleSheets = _ref.initialStyleSheets,
             err = _ref.err,
             subscription = _ref.subscription,
             isFallback = _ref.isFallback;
@@ -962,6 +963,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component: Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err: err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -970,7 +972,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1408,7 +1413,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                   as,
                   loadErrorFail
                 ) {
-                  var _yield$this$fetchComp, Component, routeInfo;
+                  var _yield$this$fetchComp, Component, styleSheets, routeInfo;
 
                   return _regeneratorRuntime.wrap(
                     function _callee2$(_context2) {
@@ -1447,38 +1452,40 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                           case 9:
                             _yield$this$fetchComp = _context2.sent;
                             Component = _yield$this$fetchComp.page;
+                            styleSheets = _yield$this$fetchComp.styleSheets;
                             routeInfo = {
                               Component: Component,
+                              styleSheets: styleSheets,
                               err: err,
                               error: err
                             };
-                            _context2.prev = 12;
-                            _context2.next = 15;
+                            _context2.prev = 13;
+                            _context2.next = 16;
                             return this.getInitialProps(Component, {
                               err: err,
                               pathname: pathname,
                               query: query
                             });
 
-                          case 15:
+                          case 16:
                             routeInfo.props = _context2.sent;
-                            _context2.next = 22;
+                            _context2.next = 23;
                             break;
 
-                          case 18:
-                            _context2.prev = 18;
-                            _context2.t0 = _context2["catch"](12);
+                          case 19:
+                            _context2.prev = 19;
+                            _context2.t0 = _context2["catch"](13);
                             console.error(
                               "Error in error page `getInitialProps`: ",
                               _context2.t0
                             );
                             routeInfo.props = {};
 
-                          case 22:
+                          case 23:
                             return _context2.abrupt("return", routeInfo);
 
-                          case 25:
-                            _context2.prev = 25;
+                          case 26:
+                            _context2.prev = 26;
                             _context2.t1 = _context2["catch"](6);
                             return _context2.abrupt(
                               "return",
@@ -1491,7 +1498,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                               )
                             );
 
-                          case 28:
+                          case 29:
                           case "end":
                             return _context2.stop();
                         }
@@ -1500,8 +1507,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                     _callee2,
                     this,
                     [
-                      [6, 25],
-                      [12, 18]
+                      [6, 26],
+                      [13, 19]
                     ]
                   );
                 })
@@ -1580,6 +1587,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                             ) {
                               return {
                                 Component: res.page,
+                                styleSheets: res.styleSheets,
                                 __N_SSG: res.mod.__N_SSG,
                                 __N_SSP: res.mod.__N_SSP
                               };
Diff for main-7635c55..431c7b079.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,7 +319,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader["default"](buildId, prefix, page);
+      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");
+          })
+      );
 
       var register = function register(_ref) {
         var _ref2 = _slicedToArray(_ref, 2),
@@ -351,6 +360,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       var Container = /*#__PURE__*/ (function(_react$default$Compon) {
@@ -543,9 +553,10 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                     case 14:
                       _yield$pageLoader$loa2 = _context.sent;
                       CachedComponent = _yield$pageLoader$loa2.page;
+                      cachedStyleSheets = _yield$pageLoader$loa2.styleSheets;
 
                       if (true) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -559,7 +570,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         (isValidElementType = _require2.isValidElementType);
 
                       if (isValidElementType(CachedComponent)) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -570,29 +581,29 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         )
                       );
 
-                    case 20:
-                      _context.next = 25;
+                    case 21:
+                      _context.next = 26;
                       break;
 
-                    case 22:
-                      _context.prev = 22;
+                    case 23:
+                      _context.prev = 23;
                       _context.t0 = _context["catch"](10);
                       // This catches errors like throwing in the top level of a module
                       initialErr = _context.t0;
 
-                    case 25:
+                    case 26:
                       if (false) {
                       }
 
                       if (!window.__NEXT_PRELOADREADY) {
-                        _context.next = 29;
+                        _context.next = 30;
                         break;
                       }
 
-                      _context.next = 29;
+                      _context.next = 30;
                       return window.__NEXT_PRELOADREADY(dynamicIds);
 
-                    case 29:
+                    case 30:
                       exports.router = router = (0, _router2.createRouter)(
                         page,
                         query,
@@ -602,16 +613,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           pageLoader: pageLoader,
                           App: CachedApp,
                           Component: CachedComponent,
+                          initialStyleSheets: cachedStyleSheets,
                           wrapApp: wrapApp,
                           err: initialErr,
                           isFallback: Boolean(isFallback),
                           subscription: function subscription(_ref5, App) {
                             var Component = _ref5.Component,
+                              styleSheets = _ref5.styleSheets,
                               props = _ref5.props,
                               err = _ref5.err;
                             return render({
                               App: App,
                               Component: Component,
+                              styleSheets: styleSheets,
                               props: props,
                               err: err
                             });
@@ -625,6 +639,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       renderCtx = {
                         App: CachedApp,
                         Component: CachedComponent,
+                        styleSheets: cachedStyleSheets,
                         props: hydrateProps,
                         err: initialErr
                       };
@@ -635,14 +650,14 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       render(renderCtx);
                       return _context.abrupt("return", emitter);
 
-                    case 37:
+                    case 38:
                       return _context.abrupt("return", {
                         emitter: emitter,
                         render: render,
                         renderCtx: renderCtx
                       });
 
-                    case 38:
+                    case 39:
                     case "end":
                       return _context.stop();
                   }
@@ -650,7 +665,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               },
               _callee,
               null,
-              [[10, 22]]
+              [[10, 23]]
             );
           })
         );
@@ -744,7 +759,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(function(_ref6) {
-          var ErrorComponent = _ref6.page;
+          var ErrorComponent = _ref6.page,
+            styleSheets = _ref6.styleSheets;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -773,6 +789,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 {
                   err: err,
                   Component: ErrorComponent,
+                  styleSheets: styleSheets,
                   props: initProps
                 }
               )
@@ -934,18 +951,104 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               Component,
               props,
               err,
+              styleSheets,
               appProps,
               resolvePromise,
               renderPromise,
+              onStart,
+              onAbort,
+              onCommit,
               elem;
             return _regeneratorRuntime.wrap(function _callee3$(_context3) {
               while (1) {
                 switch ((_context3.prev = _context3.next)) {
                   case 0:
+                    onCommit = function _onCommit() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        true
+                      ) {
+                        // Remove old stylesheets:
+                        document
+                          .querySelectorAll("link[data-n-p]")
+                          .forEach(function(el) {
+                            return el.remove();
+                          }); // 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", "");
+                          });
+                      }
+
+                      resolvePromise();
+                    };
+
+                    onAbort = function _onAbort() {
+                      document
+                        .querySelectorAll("link[data-n-staging]")
+                        .forEach(function(el) {
+                          el.remove();
+                        });
+                    };
+
+                    onStart = function _onStart() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        false
+                      ) {
+                        return Promise.resolve([]);
+                      }
+
+                      var referenceNode = [].slice
+                        .call(
+                          document.querySelectorAll(
+                            "link[data-n-g], link[data-n-p]"
+                          )
+                        )
+                        .pop();
+                      var required = styleSheets.map(function(href) {
+                        var _ref10 = (0, _pageLoader.createLink)(
+                            href,
+                            "stylesheet"
+                          ),
+                          _ref11 = _slicedToArray(_ref10, 2),
+                          link = _ref11[0],
+                          promise = _ref11[1];
+
+                        link.setAttribute("data-n-staging", "");
+                        link.setAttribute("media", "none");
+
+                        if (referenceNode) {
+                          referenceNode.parentNode.insertBefore(
+                            link,
+                            referenceNode.nextSibling
+                          );
+                          referenceNode = link;
+                        } else {
+                          document.head.appendChild(link);
+                        }
+
+                        return promise;
+                      });
+                      return Promise.all(required);
+                    };
+
                     (App = _ref8.App),
                       (Component = _ref8.Component),
                       (props = _ref8.props),
-                      (err = _ref8.err);
+                      (err = _ref8.err),
+                      (styleSheets = _ref8.styleSheets);
                     Component = Component || lastAppProps.Component;
                     props = props || lastAppProps.props;
                     appProps = (0, _extends2["default"])(
@@ -974,10 +1077,14 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         reject();
                       };
                     });
+                    renderPromise["catch"](function(abortError) {
+                      onAbort();
+                      throw abortError;
+                    });
                     elem = /*#__PURE__*/ _react["default"].createElement(
                       Root,
                       {
-                        callback: resolvePromise
+                        callback: onCommit
                       },
                       /*#__PURE__*/ _react["default"].createElement(
                         AppContainer,
@@ -989,14 +1096,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       )
                     ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+                    _context3.next = 13;
+                    return onStart();
+
+                  case 13:
+                    // TODO: test CSS loading error
                     renderReactElement(
                       false ? /*#__PURE__*/ undefined : elem,
                       appElement
                     );
-                    _context3.next = 10;
+                    _context3.next = 16;
                     return renderPromise;
 
-                  case 10:
+                  case 16:
                   case "end":
                     return _context3.stop();
                 }
@@ -1460,9 +1572,12 @@ _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"));
@@ -1506,6 +1621,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1525,23 +1641,42 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise(function(res, rej) {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise(function(res, rej) {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var _createLink = createLink(href, rel, as),
+          _createLink2 = _slicedToArray(_createLink, 2),
+          link = _createLink2[0],
+          res = _createLink2[1];
+
+        document.head.appendChild(link);
+        return res;
       }
 
       var PageLoader = /*#__PURE__*/ (function() {
-        function PageLoader(buildId, assetPrefix, initialPage) {
+        function PageLoader(
+          buildId,
+          assetPrefix,
+          initialPage,
+          initialStyleSheets
+        ) {
           _classCallCheck(this, PageLoader);
 
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1550,6 +1685,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1785,18 +1922,25 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           )
                         ) {
                           _this4.loadScript(d, route);
-                        }
+                        } // Prefetch CSS as it'll be needed when the page JavaScript
+                        // evaluates. This will only trigger if explicit prefetching is
+                        // disabled for a <Link>... prefetching in this case is desirable
+                        // because we *know* it's going to be used very soon (page was
+                        // loaded).
 
                         if (
                           d.endsWith(".css") &&
                           !document.querySelector(
-                            'link[rel=stylesheet][href^="'.concat(d, '"]')
+                            'link[rel="'
+                              .concat(relPreload, '"][href^="')
+                              .concat(d, '"]')
                           )
                         ) {
-                          appendLink(d, "stylesheet")["catch"](function() {
-                            // FIXME: handle failure
-                            // Right now, this is needed to prevent an unhandled rejection.
-                          });
+                          appendLink(d, relPreload, "style")["catch"](
+                            function() {
+                              /* ignore preload error */
+                            }
+                          );
                         }
                       });
                     });
@@ -1835,12 +1979,13 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             value: function registerPage(route, regFn) {
               var _this6 = this;
 
-              var register = function register() {
+              var register = function register(styleSheets) {
                 try {
                   var mod = regFn();
                   var pageData = {
                     page: mod["default"] || mod,
-                    mod: mod
+                    mod: mod,
+                    styleSheets: styleSheets
                   };
                   _this6.pageCache[route] = pageData;
 
@@ -1860,7 +2005,30 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
-              register();
+              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) // TODO: test this doesn't block register of initial page
+                  : this.getDependencies(route);
+              promisedDeps.then(
+                function(deps) {
+                  return register(
+                    deps.filter(function(d) {
+                      return d.endsWith(".css");
+                    })
+                  );
+                },
+                function(error) {
+                  _this6.pageCache[route] = {
+                    error: error
+                  };
+
+                  _this6.pageRegisterEvents.emit(route, {
+                    error: error
+                  });
+                }
+              );
             }
             /**
              * @param {string} route
Diff for main-a4b7a58..bc.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,7 +239,14 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader.default(buildId, prefix, page);
+      var pageLoader = new _pageLoader.default(
+        buildId,
+        prefix,
+        page,
+        [].slice
+          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
+          .map(e => e.getAttribute("href"))
+      );
 
       var register = _ref => {
         var [r, f] = _ref;
@@ -262,6 +269,7 @@
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       class Container extends _react.default.Component {
@@ -395,7 +403,10 @@
         var initialErr = hydrateErr;
 
         try {
-          ({ page: CachedComponent } = await pageLoader.loadPage(page));
+          ({
+            page: CachedComponent,
+            styleSheets: cachedStyleSheets
+          } = await pageLoader.loadPage(page));
 
           if (false) {
             var isValidElementType;
@@ -422,14 +433,16 @@
             pageLoader,
             App: CachedApp,
             Component: CachedComponent,
+            initialStyleSheets: cachedStyleSheets,
             wrapApp,
             err: initialErr,
             isFallback: Boolean(isFallback),
             subscription: (_ref3, App) => {
-              var { Component, props, err } = _ref3;
+              var { Component, styleSheets, props, err } = _ref3;
               return render({
                 App,
                 Component,
+                styleSheets,
                 props,
                 err
               });
@@ -443,6 +456,7 @@
         var renderCtx = {
           App: CachedApp,
           Component: CachedComponent,
+          styleSheets: cachedStyleSheets,
           props: hydrateProps,
           err: initialErr
         };
@@ -494,7 +508,7 @@
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(_ref4 => {
-          var { page: ErrorComponent } = _ref4;
+          var { page: ErrorComponent, styleSheets } = _ref4;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -523,6 +537,7 @@
                 {
                   err,
                   Component: ErrorComponent,
+                  styleSheets,
                   props: initProps
                 }
               )
@@ -668,7 +683,7 @@
       };
 
       async function doRender(_ref6) {
-        var { App, Component, props, err } = _ref6;
+        var { App, Component, props, err, styleSheets } = _ref6;
         Component = Component || lastAppProps.Component;
         props = props || lastAppProps.props;
         var appProps = (0, _extends2.default)(
@@ -699,10 +714,82 @@
           };
         });
 
+        function onStart() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            false
+          ) {
+            return Promise.resolve([]);
+          }
+
+          var referenceNode = [].slice
+            .call(document.querySelectorAll("link[data-n-g], link[data-n-p]"))
+            .pop();
+          var required = styleSheets.map(href => {
+            var [link, promise] = (0, _pageLoader.createLink)(
+              href,
+              "stylesheet"
+            );
+            link.setAttribute("data-n-staging", "");
+            link.setAttribute("media", "none");
+
+            if (referenceNode) {
+              referenceNode.parentNode.insertBefore(
+                link,
+                referenceNode.nextSibling
+              );
+              referenceNode = link;
+            } else {
+              document.head.appendChild(link);
+            }
+
+            return promise;
+          });
+          return Promise.all(required);
+        }
+
+        function onAbort() {
+          document.querySelectorAll("link[data-n-staging]").forEach(el => {
+            el.remove();
+          });
+        }
+
+        renderPromise.catch(abortError => {
+          onAbort();
+          throw abortError;
+        });
+
+        function onCommit() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            true
+          ) {
+            // Remove old stylesheets:
+            document
+              .querySelectorAll("link[data-n-p]")
+              .forEach(el => el.remove()); // 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", "");
+              });
+          }
+
+          resolvePromise();
+        }
+
         var elem = /*#__PURE__*/ _react.default.createElement(
           Root,
           {
-            callback: resolvePromise
+            callback: onCommit
           },
           /*#__PURE__*/ _react.default.createElement(
             AppContainer,
@@ -711,6 +798,8 @@
           )
         ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+        await onStart(); // TODO: test CSS loading error
+
         renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
         await renderPromise;
       }
@@ -1107,6 +1196,7 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
+      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1150,6 +1240,7 @@
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1169,21 +1260,31 @@
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise((res, rej) => {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise((res, rej) => {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var [link, res] = createLink(href, rel, as);
+        document.head.appendChild(link);
+        return res;
       }
 
       class PageLoader {
-        constructor(buildId, assetPrefix, initialPage) {
+        constructor(buildId, assetPrefix, initialPage, initialStyleSheets) {
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1192,6 +1293,8 @@
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1386,17 +1489,22 @@
                       !document.querySelector('script[src^="'.concat(d, '"]'))
                     ) {
                       this.loadScript(d, route);
-                    }
+                    } // Prefetch CSS as it'll be needed when the page JavaScript
+                    // evaluates. This will only trigger if explicit prefetching is
+                    // disabled for a <Link>... prefetching in this case is desirable
+                    // because we *know* it's going to be used very soon (page was
+                    // loaded).
 
                     if (
                       d.endsWith(".css") &&
                       !document.querySelector(
-                        'link[rel=stylesheet][href^="'.concat(d, '"]')
+                        'link[rel="'
+                          .concat(relPreload, '"][href^="')
+                          .concat(d, '"]')
                       )
                     ) {
-                      appendLink(d, "stylesheet").catch(() => {
-                        // FIXME: handle failure
-                        // Right now, this is needed to prevent an unhandled rejection.
+                      appendLink(d, relPreload, "style").catch(() => {
+                        /* ignore preload error */
                       });
                     }
                   });
@@ -1428,12 +1536,13 @@
         } // This method if called by the route code.
 
         registerPage(route, regFn) {
-          var register = () => {
+          var register = styleSheets => {
             try {
               var mod = regFn();
               var pageData = {
                 page: mod.default || mod,
-                mod
+                mod,
+                styleSheets
               };
               this.pageCache[route] = pageData;
               this.pageRegisterEvents.emit(route, pageData);
@@ -1451,7 +1560,23 @@
             var check;
           }
 
-          register();
+          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) // TODO: test this doesn't block register of initial page
+              : this.getDependencies(route);
+          promisedDeps.then(
+            deps => register(deps.filter(d => d.endsWith(".css"))),
+            error => {
+              this.pageCache[route] = {
+                error
+              };
+              this.pageRegisterEvents.emit(route, {
+                error
+              });
+            }
+          );
         }
         /**
          * @param {string} route
Diff for index.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      href="/_next/static/chunks/main-c493752e6f63ecb99a37.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7635c556f5f431c7b079.js"
+      src="/_next/static/chunks/main-2dccf310a7e160a5474e.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      src="/_next/static/chunks/main-c493752e6f63ecb99a37.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      href="/_next/static/chunks/main-c493752e6f63ecb99a37.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +86,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7635c556f5f431c7b079.js"
+      src="/_next/static/chunks/main-2dccf310a7e160a5474e.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      src="/_next/static/chunks/main-c493752e6f63ecb99a37.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -122,13 +122,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      href="/_next/static/chunks/main-c493752e6f63ecb99a37.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7635c556f5f431c7b079.js"
+      src="/_next/static/chunks/main-2dccf310a7e160a5474e.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-a4b7a58e3a703e0cccbc.module.js"
+      src="/_next/static/chunks/main-c493752e6f63ecb99a37.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 15.9s 16.6s ⚠️ +727ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +12.4 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB N/A N/A
framework.HASH.js gzip 39 kB 39 kB
main-8c9d3f7..4367.js gzip 6.72 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
677f882d2ed8..aa9e.js gzip N/A 10.2 kB N/A
main-2b09c43..1b9f.js gzip N/A 7.15 kB N/A
Overall change 56.7 kB 57.1 kB ⚠️ +456 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB N/A N/A
framework.HA..dule.js gzip 39 kB 39 kB
main-e89c24f..dule.js gzip 5.79 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
677f882d2ed8..dule.js gzip N/A 6.11 kB N/A
main-f068919..dule.js gzip N/A 6.16 kB N/A
Overall change 51.6 kB 52 kB ⚠️ +399 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_error.js 1.03 MB 1.03 MB ⚠️ +204 B
404.html 4.18 kB 4.18 kB
hooks.html 3.82 kB 3.82 kB
index.js 1.03 MB 1.03 MB ⚠️ +204 B
link.js 1.07 MB 1.07 MB ⚠️ +437 B
routerDirect.js 1.06 MB 1.07 MB ⚠️ +437 B
withRouter.js 1.06 MB 1.07 MB ⚠️ +437 B
Overall change 5.27 MB 5.27 MB ⚠️ +1.72 kB
Commit: 041c38f

@Timer Timer marked this pull request as ready for review August 17, 2020 18:52
@ijjk
Copy link
Member

ijjk commented Aug 17, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 12.9s 13.2s ⚠️ +268ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +15.2 kB
Page Load Tests Overall decrease ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
/ failed reqs 0 0
/ total time (seconds) 2.336 2.527 ⚠️ +0.19
/ avg req/sec 1070.35 989.16 ⚠️ -81.19
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.467 1.525 ⚠️ +0.06
/error-in-render avg req/sec 1704.65 1638.88 ⚠️ -65.77
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB 10.2 kB ⚠️ +32 B
framework.HASH.js gzip 39 kB 39 kB
main-bec7641..f131.js gzip 6.72 kB 7.17 kB ⚠️ +442 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 56.7 kB 57.1 kB ⚠️ +474 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB 6.11 kB ⚠️ +30 B
framework.HA..dule.js gzip 39 kB 39 kB
main-b7036a3..dule.js gzip 5.79 kB 6.18 kB ⚠️ +390 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 51.6 kB 52 kB ⚠️ +420 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 decrease ✓
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
index.html gzip 948 B 946 B -2 B
link.html gzip 953 B 950 B -3 B
withRouter.html gzip 939 B 938 B -1 B
Overall change 2.84 kB 2.83 kB -6 B

Diffs

Diff for 677f882d2ed8..5d.module.js
@@ -728,6 +728,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App,
             wrapApp,
             Component,
+            initialStyleSheets,
             err,
             subscription,
             isFallback
@@ -809,6 +810,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -817,7 +819,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1114,9 +1119,12 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           try {
-            var { page: Component } = await this.fetchComponent("/_error");
+            var { page: Component, styleSheets } = await this.fetchComponent(
+              "/_error"
+            );
             var routeInfo = {
               Component,
+              styleSheets,
               err,
               error: err
             };
@@ -1161,6 +1169,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
               ? cachedRouteInfo
               : await this.fetchComponent(route).then(res => ({
                   Component: res.page,
+                  styleSheets: res.styleSheets,
                   __N_SSG: res.mod.__N_SSG,
                   __N_SSP: res.mod.__N_SSP
                 }));
Diff for 677f882d2ed8..6e2c5bf9e.js
@@ -872,6 +872,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App = _ref.App,
             wrapApp = _ref.wrapApp,
             Component = _ref.Component,
+            initialStyleSheets = _ref.initialStyleSheets,
             err = _ref.err,
             subscription = _ref.subscription,
             isFallback = _ref.isFallback;
@@ -962,6 +963,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component: Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err: err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -970,7 +972,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1408,7 +1413,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                   as,
                   loadErrorFail
                 ) {
-                  var _yield$this$fetchComp, Component, routeInfo;
+                  var _yield$this$fetchComp, Component, styleSheets, routeInfo;
 
                   return _regeneratorRuntime.wrap(
                     function _callee2$(_context2) {
@@ -1447,38 +1452,40 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                           case 9:
                             _yield$this$fetchComp = _context2.sent;
                             Component = _yield$this$fetchComp.page;
+                            styleSheets = _yield$this$fetchComp.styleSheets;
                             routeInfo = {
                               Component: Component,
+                              styleSheets: styleSheets,
                               err: err,
                               error: err
                             };
-                            _context2.prev = 12;
-                            _context2.next = 15;
+                            _context2.prev = 13;
+                            _context2.next = 16;
                             return this.getInitialProps(Component, {
                               err: err,
                               pathname: pathname,
                               query: query
                             });
 
-                          case 15:
+                          case 16:
                             routeInfo.props = _context2.sent;
-                            _context2.next = 22;
+                            _context2.next = 23;
                             break;
 
-                          case 18:
-                            _context2.prev = 18;
-                            _context2.t0 = _context2["catch"](12);
+                          case 19:
+                            _context2.prev = 19;
+                            _context2.t0 = _context2["catch"](13);
                             console.error(
                               "Error in error page `getInitialProps`: ",
                               _context2.t0
                             );
                             routeInfo.props = {};
 
-                          case 22:
+                          case 23:
                             return _context2.abrupt("return", routeInfo);
 
-                          case 25:
-                            _context2.prev = 25;
+                          case 26:
+                            _context2.prev = 26;
                             _context2.t1 = _context2["catch"](6);
                             return _context2.abrupt(
                               "return",
@@ -1491,7 +1498,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                               )
                             );
 
-                          case 28:
+                          case 29:
                           case "end":
                             return _context2.stop();
                         }
@@ -1500,8 +1507,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                     _callee2,
                     this,
                     [
-                      [6, 25],
-                      [12, 18]
+                      [6, 26],
+                      [13, 19]
                     ]
                   );
                 })
@@ -1580,6 +1587,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                             ) {
                               return {
                                 Component: res.page,
+                                styleSheets: res.styleSheets,
                                 __N_SSG: res.mod.__N_SSG,
                                 __N_SSP: res.mod.__N_SSP
                               };
Diff for main-03e3ef9..36f38362a.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,7 +319,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader["default"](buildId, prefix, page);
+      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");
+          })
+      );
 
       var register = function register(_ref) {
         var _ref2 = _slicedToArray(_ref, 2),
@@ -351,6 +360,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       var Container = /*#__PURE__*/ (function(_react$default$Compon) {
@@ -543,9 +553,10 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                     case 14:
                       _yield$pageLoader$loa2 = _context.sent;
                       CachedComponent = _yield$pageLoader$loa2.page;
+                      cachedStyleSheets = _yield$pageLoader$loa2.styleSheets;
 
                       if (true) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -559,7 +570,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         (isValidElementType = _require2.isValidElementType);
 
                       if (isValidElementType(CachedComponent)) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -570,29 +581,29 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         )
                       );
 
-                    case 20:
-                      _context.next = 25;
+                    case 21:
+                      _context.next = 26;
                       break;
 
-                    case 22:
-                      _context.prev = 22;
+                    case 23:
+                      _context.prev = 23;
                       _context.t0 = _context["catch"](10);
                       // This catches errors like throwing in the top level of a module
                       initialErr = _context.t0;
 
-                    case 25:
+                    case 26:
                       if (false) {
                       }
 
                       if (!window.__NEXT_PRELOADREADY) {
-                        _context.next = 29;
+                        _context.next = 30;
                         break;
                       }
 
-                      _context.next = 29;
+                      _context.next = 30;
                       return window.__NEXT_PRELOADREADY(dynamicIds);
 
-                    case 29:
+                    case 30:
                       exports.router = router = (0, _router2.createRouter)(
                         page,
                         query,
@@ -602,16 +613,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           pageLoader: pageLoader,
                           App: CachedApp,
                           Component: CachedComponent,
+                          initialStyleSheets: cachedStyleSheets,
                           wrapApp: wrapApp,
                           err: initialErr,
                           isFallback: Boolean(isFallback),
                           subscription: function subscription(_ref5, App) {
                             var Component = _ref5.Component,
+                              styleSheets = _ref5.styleSheets,
                               props = _ref5.props,
                               err = _ref5.err;
                             return render({
                               App: App,
                               Component: Component,
+                              styleSheets: styleSheets,
                               props: props,
                               err: err
                             });
@@ -625,6 +639,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       renderCtx = {
                         App: CachedApp,
                         Component: CachedComponent,
+                        styleSheets: cachedStyleSheets,
                         props: hydrateProps,
                         err: initialErr
                       };
@@ -635,14 +650,14 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       render(renderCtx);
                       return _context.abrupt("return", emitter);
 
-                    case 37:
+                    case 38:
                       return _context.abrupt("return", {
                         emitter: emitter,
                         render: render,
                         renderCtx: renderCtx
                       });
 
-                    case 38:
+                    case 39:
                     case "end":
                       return _context.stop();
                   }
@@ -650,7 +665,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               },
               _callee,
               null,
-              [[10, 22]]
+              [[10, 23]]
             );
           })
         );
@@ -744,7 +759,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(function(_ref6) {
-          var ErrorComponent = _ref6.page;
+          var ErrorComponent = _ref6.page,
+            styleSheets = _ref6.styleSheets;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -773,6 +789,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 {
                   err: err,
                   Component: ErrorComponent,
+                  styleSheets: styleSheets,
                   props: initProps
                 }
               )
@@ -934,18 +951,122 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               Component,
               props,
               err,
+              styleSheets,
               appProps,
               resolvePromise,
               renderPromise,
+              onStart,
+              onAbort,
+              onCommit,
               elem;
             return _regeneratorRuntime.wrap(function _callee3$(_context3) {
               while (1) {
                 switch ((_context3.prev = _context3.next)) {
                   case 0:
+                    onCommit = function _onCommit() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        true
+                      ) {
+                        // Remove old stylesheets:
+                        document
+                          .querySelectorAll("link[data-n-p]")
+                          .forEach(function(el) {
+                            return el.remove();
+                          }); // 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", "");
+                          });
+                      }
+
+                      resolvePromise();
+                    };
+
+                    onAbort = function _onAbort() {
+                      document
+                        .querySelectorAll("link[data-n-staging]")
+                        .forEach(function(el) {
+                          el.remove();
+                        });
+                    };
+
+                    onStart = function _onStart() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        false
+                      ) {
+                        return Promise.resolve([]);
+                      }
+
+                      var referenceNode = [].slice
+                        .call(
+                          document.querySelectorAll(
+                            "link[data-n-g], link[data-n-p]"
+                          )
+                        )
+                        .pop();
+                      var required = styleSheets.map(function(href) {
+                        var _ref10 = (0, _pageLoader.createLink)(
+                            href,
+                            "stylesheet"
+                          ),
+                          _ref11 = _slicedToArray(_ref10, 2),
+                          link = _ref11[0],
+                          promise = _ref11[1];
+
+                        link.setAttribute("data-n-staging", "");
+                        link.setAttribute("media", "none");
+
+                        if (referenceNode) {
+                          referenceNode.parentNode.insertBefore(
+                            link,
+                            referenceNode.nextSibling
+                          );
+                          referenceNode = link;
+                        } else {
+                          document.head.appendChild(link);
+                        }
+
+                        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() {});
+                      });
+                    };
+
                     (App = _ref8.App),
                       (Component = _ref8.Component),
                       (props = _ref8.props),
-                      (err = _ref8.err);
+                      (err = _ref8.err),
+                      (styleSheets = _ref8.styleSheets);
                     Component = Component || lastAppProps.Component;
                     props = props || lastAppProps.props;
                     appProps = (0, _extends2["default"])(
@@ -973,11 +1094,20 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         _lastRenderReject = null;
                         reject();
                       };
+                    }); // 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.
+
+                    renderPromise["catch"](function(abortError) {
+                      onAbort();
+                      throw abortError;
                     });
                     elem = /*#__PURE__*/ _react["default"].createElement(
                       Root,
                       {
-                        callback: resolvePromise
+                        callback: onCommit
                       },
                       /*#__PURE__*/ _react["default"].createElement(
                         AppContainer,
@@ -989,14 +1119,18 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       )
                     ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+                    _context3.next = 13;
+                    return onStart();
+
+                  case 13:
                     renderReactElement(
                       false ? /*#__PURE__*/ undefined : elem,
                       appElement
                     );
-                    _context3.next = 10;
+                    _context3.next = 16;
                     return renderPromise;
 
-                  case 10:
+                  case 16:
                   case "end":
                     return _context3.stop();
                 }
@@ -1460,9 +1594,12 @@ _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"));
@@ -1506,6 +1643,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1525,23 +1663,42 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise(function(res, rej) {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise(function(res, rej) {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var _createLink = createLink(href, rel, as),
+          _createLink2 = _slicedToArray(_createLink, 2),
+          link = _createLink2[0],
+          res = _createLink2[1];
+
+        document.head.appendChild(link);
+        return res;
       }
 
       var PageLoader = /*#__PURE__*/ (function() {
-        function PageLoader(buildId, assetPrefix, initialPage) {
+        function PageLoader(
+          buildId,
+          assetPrefix,
+          initialPage,
+          initialStyleSheets
+        ) {
           _classCallCheck(this, PageLoader);
 
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1550,6 +1707,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1785,18 +1944,25 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           )
                         ) {
                           _this4.loadScript(d, route);
-                        }
+                        } // Prefetch CSS as it'll be needed when the page JavaScript
+                        // evaluates. This will only trigger if explicit prefetching is
+                        // disabled for a <Link>... prefetching in this case is desirable
+                        // because we *know* it's going to be used very soon (page was
+                        // loaded).
 
                         if (
                           d.endsWith(".css") &&
                           !document.querySelector(
-                            'link[rel=stylesheet][href^="'.concat(d, '"]')
+                            'link[rel="'
+                              .concat(relPreload, '"][href^="')
+                              .concat(d, '"]')
                           )
                         ) {
-                          appendLink(d, "stylesheet")["catch"](function() {
-                            // FIXME: handle failure
-                            // Right now, this is needed to prevent an unhandled rejection.
-                          });
+                          appendLink(d, relPreload, "style")["catch"](
+                            function() {
+                              /* ignore preload error */
+                            }
+                          );
                         }
                       });
                     });
@@ -1835,12 +2001,13 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             value: function registerPage(route, regFn) {
               var _this6 = this;
 
-              var register = function register() {
+              var register = function register(styleSheets) {
                 try {
                   var mod = regFn();
                   var pageData = {
                     page: mod["default"] || mod,
-                    mod: mod
+                    mod: mod,
+                    styleSheets: styleSheets
                   };
                   _this6.pageCache[route] = pageData;
 
@@ -1860,7 +2027,31 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
-              register();
+              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);
+              promisedDeps.then(
+                function(deps) {
+                  return register(
+                    deps.filter(function(d) {
+                      return d.endsWith(".css");
+                    })
+                  );
+                },
+                function(error) {
+                  _this6.pageCache[route] = {
+                    error: error
+                  };
+
+                  _this6.pageRegisterEvents.emit(route, {
+                    error: error
+                  });
+                }
+              );
             }
             /**
              * @param {string} route
Diff for main-31a3e99..c0.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,7 +239,14 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader.default(buildId, prefix, page);
+      var pageLoader = new _pageLoader.default(
+        buildId,
+        prefix,
+        page,
+        [].slice
+          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
+          .map(e => e.getAttribute("href"))
+      );
 
       var register = _ref => {
         var [r, f] = _ref;
@@ -262,6 +269,7 @@
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       class Container extends _react.default.Component {
@@ -395,7 +403,10 @@
         var initialErr = hydrateErr;
 
         try {
-          ({ page: CachedComponent } = await pageLoader.loadPage(page));
+          ({
+            page: CachedComponent,
+            styleSheets: cachedStyleSheets
+          } = await pageLoader.loadPage(page));
 
           if (false) {
             var isValidElementType;
@@ -422,14 +433,16 @@
             pageLoader,
             App: CachedApp,
             Component: CachedComponent,
+            initialStyleSheets: cachedStyleSheets,
             wrapApp,
             err: initialErr,
             isFallback: Boolean(isFallback),
             subscription: (_ref3, App) => {
-              var { Component, props, err } = _ref3;
+              var { Component, styleSheets, props, err } = _ref3;
               return render({
                 App,
                 Component,
+                styleSheets,
                 props,
                 err
               });
@@ -443,6 +456,7 @@
         var renderCtx = {
           App: CachedApp,
           Component: CachedComponent,
+          styleSheets: cachedStyleSheets,
           props: hydrateProps,
           err: initialErr
         };
@@ -494,7 +508,7 @@
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(_ref4 => {
-          var { page: ErrorComponent } = _ref4;
+          var { page: ErrorComponent, styleSheets } = _ref4;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -523,6 +537,7 @@
                 {
                   err,
                   Component: ErrorComponent,
+                  styleSheets,
                   props: initProps
                 }
               )
@@ -668,7 +683,7 @@
       };
 
       async function doRender(_ref6) {
-        var { App, Component, props, err } = _ref6;
+        var { App, Component, props, err, styleSheets } = _ref6;
         Component = Component || lastAppProps.Component;
         props = props || lastAppProps.props;
         var appProps = (0, _extends2.default)(
@@ -697,12 +712,106 @@
             lastRenderReject = null;
             reject();
           };
+        }); // 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.
+
+        function onStart() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            false
+          ) {
+            return Promise.resolve([]);
+          }
+
+          var referenceNode = [].slice
+            .call(document.querySelectorAll("link[data-n-g], link[data-n-p]"))
+            .pop();
+          var required = styleSheets.map(href => {
+            var [link, promise] = (0, _pageLoader.createLink)(
+              href,
+              "stylesheet"
+            );
+            link.setAttribute("data-n-staging", "");
+            link.setAttribute("media", "none");
+
+            if (referenceNode) {
+              referenceNode.parentNode.insertBefore(
+                link,
+                referenceNode.nextSibling
+              );
+              referenceNode = link;
+            } else {
+              document.head.appendChild(link);
+            }
+
+            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(() => {});
+          });
+        }
+
+        function onAbort() {
+          document.querySelectorAll("link[data-n-staging]").forEach(el => {
+            el.remove();
+          });
+        }
+
+        renderPromise.catch(abortError => {
+          onAbort();
+          throw abortError;
         });
 
+        function onCommit() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            true
+          ) {
+            // Remove old stylesheets:
+            document
+              .querySelectorAll("link[data-n-p]")
+              .forEach(el => el.remove()); // 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", "");
+              });
+          }
+
+          resolvePromise();
+        }
+
         var elem = /*#__PURE__*/ _react.default.createElement(
           Root,
           {
-            callback: resolvePromise
+            callback: onCommit
           },
           /*#__PURE__*/ _react.default.createElement(
             AppContainer,
@@ -711,6 +820,7 @@
           )
         ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+        await onStart();
         renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
         await renderPromise;
       }
@@ -1107,6 +1217,7 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
+      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1150,6 +1261,7 @@
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1169,21 +1281,31 @@
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise((res, rej) => {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise((res, rej) => {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var [link, res] = createLink(href, rel, as);
+        document.head.appendChild(link);
+        return res;
       }
 
       class PageLoader {
-        constructor(buildId, assetPrefix, initialPage) {
+        constructor(buildId, assetPrefix, initialPage, initialStyleSheets) {
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1192,6 +1314,8 @@
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1386,17 +1510,22 @@
                       !document.querySelector('script[src^="'.concat(d, '"]'))
                     ) {
                       this.loadScript(d, route);
-                    }
+                    } // Prefetch CSS as it'll be needed when the page JavaScript
+                    // evaluates. This will only trigger if explicit prefetching is
+                    // disabled for a <Link>... prefetching in this case is desirable
+                    // because we *know* it's going to be used very soon (page was
+                    // loaded).
 
                     if (
                       d.endsWith(".css") &&
                       !document.querySelector(
-                        'link[rel=stylesheet][href^="'.concat(d, '"]')
+                        'link[rel="'
+                          .concat(relPreload, '"][href^="')
+                          .concat(d, '"]')
                       )
                     ) {
-                      appendLink(d, "stylesheet").catch(() => {
-                        // FIXME: handle failure
-                        // Right now, this is needed to prevent an unhandled rejection.
+                      appendLink(d, relPreload, "style").catch(() => {
+                        /* ignore preload error */
                       });
                     }
                   });
@@ -1428,12 +1557,13 @@
         } // This method if called by the route code.
 
         registerPage(route, regFn) {
-          var register = () => {
+          var register = styleSheets => {
             try {
               var mod = regFn();
               var pageData = {
                 page: mod.default || mod,
-                mod
+                mod,
+                styleSheets
               };
               this.pageCache[route] = pageData;
               this.pageRegisterEvents.emit(route, pageData);
@@ -1451,7 +1581,24 @@
             var check;
           }
 
-          register();
+          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);
+          promisedDeps.then(
+            deps => register(deps.filter(d => d.endsWith(".css"))),
+            error => {
+              this.pageCache[route] = {
+                error
+              };
+              this.pageRegisterEvents.emit(route, {
+                error
+              });
+            }
+          );
         }
         /**
          * @param {string} route
Diff for index.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      href="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-03e3ef9a28236f38362a.js"
+      src="/_next/static/chunks/main-63c2d02cec94cc7688b5.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      src="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      href="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +86,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-03e3ef9a28236f38362a.js"
+      src="/_next/static/chunks/main-63c2d02cec94cc7688b5.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      src="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -122,13 +122,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      href="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-03e3ef9a28236f38362a.js"
+      src="/_next/static/chunks/main-63c2d02cec94cc7688b5.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      src="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 14.5s 15.3s ⚠️ +830ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +15.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB N/A N/A
framework.HASH.js gzip 39 kB 39 kB
main-bec7641..f131.js gzip 6.72 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
677f882d2ed8..aa9e.js gzip N/A 10.2 kB N/A
main-8ce57b4..33a2.js gzip N/A 7.17 kB N/A
Overall change 56.7 kB 57.1 kB ⚠️ +474 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB N/A N/A
framework.HA..dule.js gzip 39 kB 39 kB
main-b7036a3..dule.js gzip 5.79 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
677f882d2ed8..dule.js gzip N/A 6.11 kB N/A
main-2e2a2fb..dule.js gzip N/A 6.18 kB N/A
Overall change 51.6 kB 52 kB ⚠️ +420 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_error.js 1.03 MB 1.03 MB ⚠️ +204 B
404.html 4.18 kB 4.18 kB
hooks.html 3.82 kB 3.82 kB
index.js 1.03 MB 1.03 MB ⚠️ +204 B
link.js 1.07 MB 1.07 MB ⚠️ +437 B
routerDirect.js 1.06 MB 1.07 MB ⚠️ +437 B
withRouter.js 1.06 MB 1.07 MB ⚠️ +437 B
Overall change 5.27 MB 5.27 MB ⚠️ +1.72 kB
Commit: 1a68e14

@ijjk
Copy link
Member

ijjk commented Aug 17, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 12.6s 12.7s ⚠️ +134ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +15.2 kB
Page Load Tests Overall increase ✓
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
/ failed reqs 0 0
/ total time (seconds) 2.316 2.235 -0.08
/ avg req/sec 1079.49 1118.61 +39.12
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.343 1.29 -0.05
/error-in-render avg req/sec 1861.01 1938.41 +77.4
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB 10.2 kB ⚠️ +32 B
framework.HASH.js gzip 39 kB 39 kB
main-bec7641..f131.js gzip 6.72 kB 7.17 kB ⚠️ +442 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 56.7 kB 57.1 kB ⚠️ +474 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB 6.11 kB ⚠️ +30 B
framework.HA..dule.js gzip 39 kB 39 kB
main-b7036a3..dule.js gzip 5.79 kB 6.18 kB ⚠️ +390 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 51.6 kB 52 kB ⚠️ +420 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 decrease ✓
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
index.html gzip 948 B 946 B -2 B
link.html gzip 953 B 950 B -3 B
withRouter.html gzip 939 B 938 B -1 B
Overall change 2.84 kB 2.83 kB -6 B

Diffs

Diff for 677f882d2ed8..5d.module.js
@@ -728,6 +728,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App,
             wrapApp,
             Component,
+            initialStyleSheets,
             err,
             subscription,
             isFallback
@@ -809,6 +810,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -817,7 +819,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1114,9 +1119,12 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           try {
-            var { page: Component } = await this.fetchComponent("/_error");
+            var { page: Component, styleSheets } = await this.fetchComponent(
+              "/_error"
+            );
             var routeInfo = {
               Component,
+              styleSheets,
               err,
               error: err
             };
@@ -1161,6 +1169,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
               ? cachedRouteInfo
               : await this.fetchComponent(route).then(res => ({
                   Component: res.page,
+                  styleSheets: res.styleSheets,
                   __N_SSG: res.mod.__N_SSG,
                   __N_SSP: res.mod.__N_SSP
                 }));
Diff for 677f882d2ed8..6e2c5bf9e.js
@@ -872,6 +872,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
             App = _ref.App,
             wrapApp = _ref.wrapApp,
             Component = _ref.Component,
+            initialStyleSheets = _ref.initialStyleSheets,
             err = _ref.err,
             subscription = _ref.subscription,
             isFallback = _ref.isFallback;
@@ -962,6 +963,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           if (_pathname !== "/_error") {
             this.components[this.route] = {
               Component: Component,
+              styleSheets: initialStyleSheets,
               props: initialProps,
               err: err,
               __N_SSG: initialProps && initialProps.__N_SSG,
@@ -970,7 +972,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
           }
 
           this.components["/_app"] = {
-            Component: App
+            Component: App,
+            styleSheets: [
+              /* /_app does not need its stylesheets managed */
+            ]
           }; // Backwards compat for Router.router.events
           // TODO: Should be remove the following major version as it was never documented
 
@@ -1408,7 +1413,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                   as,
                   loadErrorFail
                 ) {
-                  var _yield$this$fetchComp, Component, routeInfo;
+                  var _yield$this$fetchComp, Component, styleSheets, routeInfo;
 
                   return _regeneratorRuntime.wrap(
                     function _callee2$(_context2) {
@@ -1447,38 +1452,40 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                           case 9:
                             _yield$this$fetchComp = _context2.sent;
                             Component = _yield$this$fetchComp.page;
+                            styleSheets = _yield$this$fetchComp.styleSheets;
                             routeInfo = {
                               Component: Component,
+                              styleSheets: styleSheets,
                               err: err,
                               error: err
                             };
-                            _context2.prev = 12;
-                            _context2.next = 15;
+                            _context2.prev = 13;
+                            _context2.next = 16;
                             return this.getInitialProps(Component, {
                               err: err,
                               pathname: pathname,
                               query: query
                             });
 
-                          case 15:
+                          case 16:
                             routeInfo.props = _context2.sent;
-                            _context2.next = 22;
+                            _context2.next = 23;
                             break;
 
-                          case 18:
-                            _context2.prev = 18;
-                            _context2.t0 = _context2["catch"](12);
+                          case 19:
+                            _context2.prev = 19;
+                            _context2.t0 = _context2["catch"](13);
                             console.error(
                               "Error in error page `getInitialProps`: ",
                               _context2.t0
                             );
                             routeInfo.props = {};
 
-                          case 22:
+                          case 23:
                             return _context2.abrupt("return", routeInfo);
 
-                          case 25:
-                            _context2.prev = 25;
+                          case 26:
+                            _context2.prev = 26;
                             _context2.t1 = _context2["catch"](6);
                             return _context2.abrupt(
                               "return",
@@ -1491,7 +1498,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                               )
                             );
 
-                          case 28:
+                          case 29:
                           case "end":
                             return _context2.stop();
                         }
@@ -1500,8 +1507,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                     _callee2,
                     this,
                     [
-                      [6, 25],
-                      [12, 18]
+                      [6, 26],
+                      [13, 19]
                     ]
                   );
                 })
@@ -1580,6 +1587,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
                             ) {
                               return {
                                 Component: res.page,
+                                styleSheets: res.styleSheets,
                                 __N_SSG: res.mod.__N_SSG,
                                 __N_SSP: res.mod.__N_SSP
                               };
Diff for main-03e3ef9..36f38362a.js
@@ -275,7 +275,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -319,7 +319,16 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader["default"](buildId, prefix, page);
+      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");
+          })
+      );
 
       var register = function register(_ref) {
         var _ref2 = _slicedToArray(_ref, 2),
@@ -351,6 +360,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       var Container = /*#__PURE__*/ (function(_react$default$Compon) {
@@ -543,9 +553,10 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                     case 14:
                       _yield$pageLoader$loa2 = _context.sent;
                       CachedComponent = _yield$pageLoader$loa2.page;
+                      cachedStyleSheets = _yield$pageLoader$loa2.styleSheets;
 
                       if (true) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -559,7 +570,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         (isValidElementType = _require2.isValidElementType);
 
                       if (isValidElementType(CachedComponent)) {
-                        _context.next = 20;
+                        _context.next = 21;
                         break;
                       }
 
@@ -570,29 +581,29 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         )
                       );
 
-                    case 20:
-                      _context.next = 25;
+                    case 21:
+                      _context.next = 26;
                       break;
 
-                    case 22:
-                      _context.prev = 22;
+                    case 23:
+                      _context.prev = 23;
                       _context.t0 = _context["catch"](10);
                       // This catches errors like throwing in the top level of a module
                       initialErr = _context.t0;
 
-                    case 25:
+                    case 26:
                       if (false) {
                       }
 
                       if (!window.__NEXT_PRELOADREADY) {
-                        _context.next = 29;
+                        _context.next = 30;
                         break;
                       }
 
-                      _context.next = 29;
+                      _context.next = 30;
                       return window.__NEXT_PRELOADREADY(dynamicIds);
 
-                    case 29:
+                    case 30:
                       exports.router = router = (0, _router2.createRouter)(
                         page,
                         query,
@@ -602,16 +613,19 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           pageLoader: pageLoader,
                           App: CachedApp,
                           Component: CachedComponent,
+                          initialStyleSheets: cachedStyleSheets,
                           wrapApp: wrapApp,
                           err: initialErr,
                           isFallback: Boolean(isFallback),
                           subscription: function subscription(_ref5, App) {
                             var Component = _ref5.Component,
+                              styleSheets = _ref5.styleSheets,
                               props = _ref5.props,
                               err = _ref5.err;
                             return render({
                               App: App,
                               Component: Component,
+                              styleSheets: styleSheets,
                               props: props,
                               err: err
                             });
@@ -625,6 +639,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       renderCtx = {
                         App: CachedApp,
                         Component: CachedComponent,
+                        styleSheets: cachedStyleSheets,
                         props: hydrateProps,
                         err: initialErr
                       };
@@ -635,14 +650,14 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       render(renderCtx);
                       return _context.abrupt("return", emitter);
 
-                    case 37:
+                    case 38:
                       return _context.abrupt("return", {
                         emitter: emitter,
                         render: render,
                         renderCtx: renderCtx
                       });
 
-                    case 38:
+                    case 39:
                     case "end":
                       return _context.stop();
                   }
@@ -650,7 +665,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               },
               _callee,
               null,
-              [[10, 22]]
+              [[10, 23]]
             );
           })
         );
@@ -744,7 +759,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(function(_ref6) {
-          var ErrorComponent = _ref6.page;
+          var ErrorComponent = _ref6.page,
+            styleSheets = _ref6.styleSheets;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -773,6 +789,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 {
                   err: err,
                   Component: ErrorComponent,
+                  styleSheets: styleSheets,
                   props: initProps
                 }
               )
@@ -934,18 +951,122 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
               Component,
               props,
               err,
+              styleSheets,
               appProps,
               resolvePromise,
               renderPromise,
+              onStart,
+              onAbort,
+              onCommit,
               elem;
             return _regeneratorRuntime.wrap(function _callee3$(_context3) {
               while (1) {
                 switch ((_context3.prev = _context3.next)) {
                   case 0:
+                    onCommit = function _onCommit() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        true
+                      ) {
+                        // Remove old stylesheets:
+                        document
+                          .querySelectorAll("link[data-n-p]")
+                          .forEach(function(el) {
+                            return el.remove();
+                          }); // 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", "");
+                          });
+                      }
+
+                      resolvePromise();
+                    };
+
+                    onAbort = function _onAbort() {
+                      document
+                        .querySelectorAll("link[data-n-staging]")
+                        .forEach(function(el) {
+                          el.remove();
+                        });
+                    };
+
+                    onStart = function _onStart() {
+                      if (
+                        // We can skip this during hydration. Running it wont cause any harm, but
+                        // we may as well save the CPU cycles.
+                        isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+                        // unless we're in production:
+                        false
+                      ) {
+                        return Promise.resolve([]);
+                      }
+
+                      var referenceNode = [].slice
+                        .call(
+                          document.querySelectorAll(
+                            "link[data-n-g], link[data-n-p]"
+                          )
+                        )
+                        .pop();
+                      var required = styleSheets.map(function(href) {
+                        var _ref10 = (0, _pageLoader.createLink)(
+                            href,
+                            "stylesheet"
+                          ),
+                          _ref11 = _slicedToArray(_ref10, 2),
+                          link = _ref11[0],
+                          promise = _ref11[1];
+
+                        link.setAttribute("data-n-staging", "");
+                        link.setAttribute("media", "none");
+
+                        if (referenceNode) {
+                          referenceNode.parentNode.insertBefore(
+                            link,
+                            referenceNode.nextSibling
+                          );
+                          referenceNode = link;
+                        } else {
+                          document.head.appendChild(link);
+                        }
+
+                        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() {});
+                      });
+                    };
+
                     (App = _ref8.App),
                       (Component = _ref8.Component),
                       (props = _ref8.props),
-                      (err = _ref8.err);
+                      (err = _ref8.err),
+                      (styleSheets = _ref8.styleSheets);
                     Component = Component || lastAppProps.Component;
                     props = props || lastAppProps.props;
                     appProps = (0, _extends2["default"])(
@@ -973,11 +1094,20 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                         _lastRenderReject = null;
                         reject();
                       };
+                    }); // 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.
+
+                    renderPromise["catch"](function(abortError) {
+                      onAbort();
+                      throw abortError;
                     });
                     elem = /*#__PURE__*/ _react["default"].createElement(
                       Root,
                       {
-                        callback: resolvePromise
+                        callback: onCommit
                       },
                       /*#__PURE__*/ _react["default"].createElement(
                         AppContainer,
@@ -989,14 +1119,18 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                       )
                     ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+                    _context3.next = 13;
+                    return onStart();
+
+                  case 13:
                     renderReactElement(
                       false ? /*#__PURE__*/ undefined : elem,
                       appElement
                     );
-                    _context3.next = 10;
+                    _context3.next = 16;
                     return renderPromise;
 
-                  case 10:
+                  case 16:
                   case "end":
                     return _context3.stop();
                 }
@@ -1460,9 +1594,12 @@ _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"));
@@ -1506,6 +1643,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1525,23 +1663,42 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise(function(res, rej) {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise(function(res, rej) {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var _createLink = createLink(href, rel, as),
+          _createLink2 = _slicedToArray(_createLink, 2),
+          link = _createLink2[0],
+          res = _createLink2[1];
+
+        document.head.appendChild(link);
+        return res;
       }
 
       var PageLoader = /*#__PURE__*/ (function() {
-        function PageLoader(buildId, assetPrefix, initialPage) {
+        function PageLoader(
+          buildId,
+          assetPrefix,
+          initialPage,
+          initialStyleSheets
+        ) {
           _classCallCheck(this, PageLoader);
 
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1550,6 +1707,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1785,18 +1944,25 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                           )
                         ) {
                           _this4.loadScript(d, route);
-                        }
+                        } // Prefetch CSS as it'll be needed when the page JavaScript
+                        // evaluates. This will only trigger if explicit prefetching is
+                        // disabled for a <Link>... prefetching in this case is desirable
+                        // because we *know* it's going to be used very soon (page was
+                        // loaded).
 
                         if (
                           d.endsWith(".css") &&
                           !document.querySelector(
-                            'link[rel=stylesheet][href^="'.concat(d, '"]')
+                            'link[rel="'
+                              .concat(relPreload, '"][href^="')
+                              .concat(d, '"]')
                           )
                         ) {
-                          appendLink(d, "stylesheet")["catch"](function() {
-                            // FIXME: handle failure
-                            // Right now, this is needed to prevent an unhandled rejection.
-                          });
+                          appendLink(d, relPreload, "style")["catch"](
+                            function() {
+                              /* ignore preload error */
+                            }
+                          );
                         }
                       });
                     });
@@ -1835,12 +2001,13 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             value: function registerPage(route, regFn) {
               var _this6 = this;
 
-              var register = function register() {
+              var register = function register(styleSheets) {
                 try {
                   var mod = regFn();
                   var pageData = {
                     page: mod["default"] || mod,
-                    mod: mod
+                    mod: mod,
+                    styleSheets: styleSheets
                   };
                   _this6.pageCache[route] = pageData;
 
@@ -1860,7 +2027,31 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
                 var check;
               }
 
-              register();
+              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);
+              promisedDeps.then(
+                function(deps) {
+                  return register(
+                    deps.filter(function(d) {
+                      return d.endsWith(".css");
+                    })
+                  );
+                },
+                function(error) {
+                  _this6.pageCache[route] = {
+                    error: error
+                  };
+
+                  _this6.pageRegisterEvents.emit(route, {
+                    error: error
+                  });
+                }
+              );
             }
             /**
              * @param {string} route
Diff for main-31a3e99..c0.module.js
@@ -193,7 +193,7 @@
 
       var _headManager = _interopRequireDefault(__webpack_require__("DqTX"));
 
-      var _pageLoader = _interopRequireDefault(__webpack_require__("zmvN"));
+      var _pageLoader = _interopRequireWildcard3(__webpack_require__("zmvN"));
 
       var _performanceRelayer = _interopRequireDefault(
         __webpack_require__("bGXG")
@@ -239,7 +239,14 @@
         asPath = (0, _router.delBasePath)(asPath);
       }
 
-      var pageLoader = new _pageLoader.default(buildId, prefix, page);
+      var pageLoader = new _pageLoader.default(
+        buildId,
+        prefix,
+        page,
+        [].slice
+          .call(document.querySelectorAll("link[rel=stylesheet][data-n-p]"))
+          .map(e => e.getAttribute("href"))
+      );
 
       var register = _ref => {
         var [r, f] = _ref;
@@ -262,6 +269,7 @@
       var router;
       exports.router = router;
       var CachedComponent;
+      var cachedStyleSheets;
       var CachedApp, onPerfEntry;
 
       class Container extends _react.default.Component {
@@ -395,7 +403,10 @@
         var initialErr = hydrateErr;
 
         try {
-          ({ page: CachedComponent } = await pageLoader.loadPage(page));
+          ({
+            page: CachedComponent,
+            styleSheets: cachedStyleSheets
+          } = await pageLoader.loadPage(page));
 
           if (false) {
             var isValidElementType;
@@ -422,14 +433,16 @@
             pageLoader,
             App: CachedApp,
             Component: CachedComponent,
+            initialStyleSheets: cachedStyleSheets,
             wrapApp,
             err: initialErr,
             isFallback: Boolean(isFallback),
             subscription: (_ref3, App) => {
-              var { Component, props, err } = _ref3;
+              var { Component, styleSheets, props, err } = _ref3;
               return render({
                 App,
                 Component,
+                styleSheets,
                 props,
                 err
               });
@@ -443,6 +456,7 @@
         var renderCtx = {
           App: CachedApp,
           Component: CachedComponent,
+          styleSheets: cachedStyleSheets,
           props: hydrateProps,
           err: initialErr
         };
@@ -494,7 +508,7 @@
 
         console.error(err);
         return pageLoader.loadPage("/_error").then(_ref4 => {
-          var { page: ErrorComponent } = _ref4;
+          var { page: ErrorComponent, styleSheets } = _ref4;
           // In production we do a normal render with the `ErrorComponent` as component.
           // If we've gotten here upon initial render, we can use the props from the server.
           // Otherwise, we need to call `getInitialProps` on `App` before mounting.
@@ -523,6 +537,7 @@
                 {
                   err,
                   Component: ErrorComponent,
+                  styleSheets,
                   props: initProps
                 }
               )
@@ -668,7 +683,7 @@
       };
 
       async function doRender(_ref6) {
-        var { App, Component, props, err } = _ref6;
+        var { App, Component, props, err, styleSheets } = _ref6;
         Component = Component || lastAppProps.Component;
         props = props || lastAppProps.props;
         var appProps = (0, _extends2.default)(
@@ -697,12 +712,106 @@
             lastRenderReject = null;
             reject();
           };
+        }); // 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.
+
+        function onStart() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            isInitialRender || // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            false
+          ) {
+            return Promise.resolve([]);
+          }
+
+          var referenceNode = [].slice
+            .call(document.querySelectorAll("link[data-n-g], link[data-n-p]"))
+            .pop();
+          var required = styleSheets.map(href => {
+            var [link, promise] = (0, _pageLoader.createLink)(
+              href,
+              "stylesheet"
+            );
+            link.setAttribute("data-n-staging", "");
+            link.setAttribute("media", "none");
+
+            if (referenceNode) {
+              referenceNode.parentNode.insertBefore(
+                link,
+                referenceNode.nextSibling
+              );
+              referenceNode = link;
+            } else {
+              document.head.appendChild(link);
+            }
+
+            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(() => {});
+          });
+        }
+
+        function onAbort() {
+          document.querySelectorAll("link[data-n-staging]").forEach(el => {
+            el.remove();
+          });
+        }
+
+        renderPromise.catch(abortError => {
+          onAbort();
+          throw abortError;
         });
 
+        function onCommit() {
+          if (
+            // We can skip this during hydration. Running it wont cause any harm, but
+            // we may as well save the CPU cycles.
+            !isInitialRender && // We use `style-loader` in development, so we don't need to do anything
+            // unless we're in production:
+            true
+          ) {
+            // Remove old stylesheets:
+            document
+              .querySelectorAll("link[data-n-p]")
+              .forEach(el => el.remove()); // 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", "");
+              });
+          }
+
+          resolvePromise();
+        }
+
         var elem = /*#__PURE__*/ _react.default.createElement(
           Root,
           {
-            callback: resolvePromise
+            callback: onCommit
           },
           /*#__PURE__*/ _react.default.createElement(
             AppContainer,
@@ -711,6 +820,7 @@
           )
         ); // We catch runtime errors using componentDidCatch which will trigger renderError
 
+        await onStart();
         renderReactElement(false ? /*#__PURE__*/ undefined : elem, appElement);
         await renderPromise;
       }
@@ -1107,6 +1217,7 @@
       var _interopRequireDefault = __webpack_require__("TqRt");
 
       exports.__esModule = true;
+      exports.createLink = createLink;
       exports.default = void 0;
 
       var _mitt = _interopRequireDefault(__webpack_require__("dZ6Y"));
@@ -1150,6 +1261,7 @@
             "preload" // https://caniuse.com/#feat=link-rel-prefetch
           : // IE 11, Edge 12+, nearly all evergreen
             "prefetch";
+      var relPreload = hasRel("preload") ? "preload" : relPrefetch;
       var hasNoModule = "noModule" in document.createElement("script");
 
       var requestIdleCallback =
@@ -1169,21 +1281,31 @@
         return route.replace(/\/$/, "");
       }
 
-      function appendLink(href, rel, as, link) {
-        return new Promise((res, rej) => {
-          link = document.createElement("link");
-          link.crossOrigin = "anonymous";
-          link.href = href;
-          link.rel = rel;
-          if (as) link.as = as;
-          link.onload = res;
-          link.onerror = rej;
-          document.head.appendChild(link);
-        });
+      function createLink(href, rel, as, link) {
+        link = document.createElement("link");
+        return [
+          link,
+          new Promise((res, rej) => {
+            link.crossOrigin = "anonymous";
+            link.href = href;
+            link.rel = rel;
+            if (as) link.as = as;
+            link.onload = res;
+            link.onerror = rej;
+          })
+        ];
+      }
+
+      function appendLink(href, rel, as) {
+        var [link, res] = createLink(href, rel, as);
+        document.head.appendChild(link);
+        return res;
       }
 
       class PageLoader {
-        constructor(buildId, assetPrefix, initialPage) {
+        constructor(buildId, assetPrefix, initialPage, initialStyleSheets) {
+          this.initialPage = void 0;
+          this.initialStyleSheets = void 0;
           this.buildId = void 0;
           this.assetPrefix = void 0;
           this.pageCache = void 0;
@@ -1192,6 +1314,8 @@
           this.promisedBuildManifest = void 0;
           this.promisedSsgManifest = void 0;
           this.promisedDevPagesManifest = void 0;
+          this.initialPage = initialPage;
+          this.initialStyleSheets = initialStyleSheets;
           this.buildId = buildId;
           this.assetPrefix = assetPrefix;
           this.pageCache = {};
@@ -1386,17 +1510,22 @@
                       !document.querySelector('script[src^="'.concat(d, '"]'))
                     ) {
                       this.loadScript(d, route);
-                    }
+                    } // Prefetch CSS as it'll be needed when the page JavaScript
+                    // evaluates. This will only trigger if explicit prefetching is
+                    // disabled for a <Link>... prefetching in this case is desirable
+                    // because we *know* it's going to be used very soon (page was
+                    // loaded).
 
                     if (
                       d.endsWith(".css") &&
                       !document.querySelector(
-                        'link[rel=stylesheet][href^="'.concat(d, '"]')
+                        'link[rel="'
+                          .concat(relPreload, '"][href^="')
+                          .concat(d, '"]')
                       )
                     ) {
-                      appendLink(d, "stylesheet").catch(() => {
-                        // FIXME: handle failure
-                        // Right now, this is needed to prevent an unhandled rejection.
+                      appendLink(d, relPreload, "style").catch(() => {
+                        /* ignore preload error */
                       });
                     }
                   });
@@ -1428,12 +1557,13 @@
         } // This method if called by the route code.
 
         registerPage(route, regFn) {
-          var register = () => {
+          var register = styleSheets => {
             try {
               var mod = regFn();
               var pageData = {
                 page: mod.default || mod,
-                mod
+                mod,
+                styleSheets
               };
               this.pageCache[route] = pageData;
               this.pageRegisterEvents.emit(route, pageData);
@@ -1451,7 +1581,24 @@
             var check;
           }
 
-          register();
+          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);
+          promisedDeps.then(
+            deps => register(deps.filter(d => d.endsWith(".css"))),
+            error => {
+              this.pageCache[route] = {
+                error
+              };
+              this.pageRegisterEvents.emit(route, {
+                error
+              });
+            }
+          );
         }
         /**
          * @param {string} route
Diff for index.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      href="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-03e3ef9a28236f38362a.js"
+      src="/_next/static/chunks/main-63c2d02cec94cc7688b5.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      src="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      href="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -86,13 +86,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-03e3ef9a28236f38362a.js"
+      src="/_next/static/chunks/main-63c2d02cec94cc7688b5.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      src="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -122,13 +122,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -6,7 +6,7 @@
     <meta name="next-head-count" content="2" />
     <link
       rel="preload"
-      href="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      href="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -24,7 +24,7 @@
     />
     <link
       rel="preload"
-      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      href="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -81,13 +81,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-03e3ef9a28236f38362a.js"
+      src="/_next/static/chunks/main-63c2d02cec94cc7688b5.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-31a3e99ca9a60e6d0ac0.module.js"
+      src="/_next/static/chunks/main-697eecb8e86c45466ba5.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
@@ -117,13 +117,13 @@
       type="module"
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.fedf4a6cbf46e2c5bf9e.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.1f9e398db539170937cf.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.897f5e72583be4f5895d.module.js"
+      src="/_next/static/chunks/677f882d2ed86fa3467b8979053c1a4c3f8bc4df.8dfd76dca4c6b6d5a913.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
buildDuration 14.2s 14.5s ⚠️ +358ms
nodeModulesSize 57.6 MB 57.6 MB ⚠️ +15.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..b4e6.js gzip 10.2 kB N/A N/A
framework.HASH.js gzip 39 kB 39 kB
main-bec7641..f131.js gzip 6.72 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
677f882d2ed8..aa9e.js gzip N/A 10.2 kB N/A
main-8ce57b4..33a2.js gzip N/A 7.17 kB N/A
Overall change 56.7 kB 57.1 kB ⚠️ +474 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
677f882d2ed8..dule.js gzip 6.08 kB N/A N/A
framework.HA..dule.js gzip 39 kB 39 kB
main-b7036a3..dule.js gzip 5.79 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
677f882d2ed8..dule.js gzip N/A 6.11 kB N/A
main-2e2a2fb..dule.js gzip N/A 6.18 kB N/A
Overall change 51.6 kB 52 kB ⚠️ +420 B
Legacy Client Bundles (polyfills)
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-1464c..a26f.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-00b8972..6e4e.js gzip 1.3 kB 1.3 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.7 kB 7.7 kB
Client Pages Modern
vercel/next.js canary Timer/next.js hotfix/css-prefetching Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-e550f..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-72c64d9..dule.js gzip 1.27 kB 1.27 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary Timer/next.js hotfix/css-prefetching 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 hotfix/css-prefetching Change
_error.js 1.03 MB 1.03 MB ⚠️ +204 B
404.html 4.18 kB 4.18 kB
hooks.html 3.82 kB 3.82 kB
index.js 1.03 MB 1.03 MB ⚠️ +204 B
link.js 1.07 MB 1.07 MB ⚠️ +437 B
routerDirect.js 1.06 MB 1.07 MB ⚠️ +437 B
withRouter.js 1.06 MB 1.07 MB ⚠️ +437 B
Overall change 5.27 MB 5.27 MB ⚠️ +1.72 kB
Commit: fc6ab98

@Timer Timer requested a review from prateekbh August 17, 2020 20:54
@kodiakhq kodiakhq bot merged commit 06d8acd into vercel:canary Aug 17, 2020
@Timer Timer deleted the hotfix/css-prefetching branch August 17, 2020 23:01
kodiakhq bot pushed a commit that referenced this pull request Aug 18, 2020
This fixes an error that was occurring in ie11 due to `forEach` being called on `querySelectorAll` before it was massaged to an array.

x-ref: #16126
Fixes #16283
m-lautenbach pushed a commit to m-lautenbach/next.js that referenced this pull request Aug 20, 2020
This pull request adds a test case for the reproduction provided in vercel#12445. This bug is specifically caused when loading the next page before navigation has actually occurred.

---

Fixes vercel#12445
m-lautenbach pushed a commit to m-lautenbach/next.js that referenced this pull request Aug 20, 2020
This fixes an error that was occurring in ie11 due to `forEach` being called on `querySelectorAll` before it was massaged to an array.

x-ref: vercel#16126
Fixes vercel#16283
@vercel vercel locked as resolved and limited conversation to collaborators Jan 30, 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.

CSS Modules and prefetch load styles in wrong order
4 participants