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

Remove next-head-count #16758

Merged
merged 23 commits into from
Sep 9, 2020
Merged

Remove next-head-count #16758

merged 23 commits into from
Sep 9, 2020

Conversation

devknoll
Copy link
Contributor

@devknoll devknoll commented Sep 1, 2020

Removes next-head-count, improving support for 3rd party libraries that insert or append new elements to <head>.


This is more or less what a solution with a data- attribute would look like, except that instead of directly searching for elements with that attribute, we serialize the elements expected in <head> and then find them/assume ownership of them during initialization (in a manner similar to React's reconciliation) based on their properties.

There are two main assumptions here:

  1. Content is served with compression, so duplicate serialization of e.g. inline script or style tags doesn't have a meaningful impact. Storing a hash would be a potential optimization.
  2. 3rd party libraries primarily only insert new, unique elements to head. Libraries trying to actively manage elements that overlap with those that Next.js claims ownership of will still be unsupported.

The reason for this roundabout approach is that I'd really like to avoid data- if possible, for maximum compatibility. Implicitly adding an attribute could be a breaking change for some class of tools or crawlers and makes it otherwise impossible to insert raw HTML into <head>. Adding an unexpected attribute is why the original class="next-head" approach was problematic in the first place!

That said, while I don't expect this to be more problematic than next-head-count (anything that would break in this new model also should have broken in the old model), if that does end up being the case, it might make sense to just bite the bullet.

Fixes #11012
Closes #16707


cc @Timer @timneutkens

@ijjk
Copy link
Member

ijjk commented Sep 1, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 13.8s 13.6s -193ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Page Load Tests Overall increase ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.509 2.321 -0.19
/ avg req/sec 996.23 1076.97 +80.74
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.379 1.384 0
/error-in-render avg req/sec 1812.78 1805.99 ⚠️ -6.79
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..9ff9.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-cc92da0..0b67.js gzip 7.35 kB 7.71 kB ⚠️ +360 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.13 kB 6.13 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-0ed168f..dule.js gzip 6.41 kB 6.32 kB -99 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.2 kB -99 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-ae98065..267e.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-cb244c4..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 323 B 323 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 652 B 652 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 970 B 1.01 kB ⚠️ +36 B
link.html gzip 976 B 1.01 kB ⚠️ +34 B
withRouter.html gzip 964 B 999 B ⚠️ +35 B
Overall change 2.91 kB 3.02 kB ⚠️ +105 B

Diffs

Diff for main-0ed0039..df.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,55 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
-
-        if (false) {
-        }
-
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
+            if (title !== document.title) document.title = title;
+            return;
           }
-        }
 
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
+          var newTag = reactElementToDOM(tag);
 
+          for (var oldTag of elements) {
             if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+              oldTags.delete(oldTag);
+              return;
             }
           }
 
-          return true;
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        for (var oldTag of oldTags) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements.delete(oldTag);
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(entry =>
+            /*#__PURE__*/ (0, _react.createElement)(entry.type, entry.props)
+          ),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +110,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +213,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +247,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for main-4b573dc..f6f186bef.js
@@ -42,8 +42,85 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      function _createForOfIteratorHelper(o, allowArrayLike) {
+        var it;
+        if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
+          if (
+            Array.isArray(o) ||
+            (it = _unsupportedIterableToArray(o)) ||
+            (allowArrayLike && o && typeof o.length === "number")
+          ) {
+            if (it) o = it;
+            var i = 0;
+            var F = function F() {};
+            return {
+              s: F,
+              n: function n() {
+                if (i >= o.length) return { done: true };
+                return { done: false, value: o[i++] };
+              },
+              e: function e(_e) {
+                throw _e;
+              },
+              f: F
+            };
+          }
+          throw new TypeError(
+            "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
+          );
+        }
+        var normalCompletion = true,
+          didErr = false,
+          err;
+        return {
+          s: function s() {
+            it = o[Symbol.iterator]();
+          },
+          n: function n() {
+            var step = it.next();
+            normalCompletion = step.done;
+            return step;
+          },
+          e: function e(_e2) {
+            didErr = true;
+            err = _e2;
+          },
+          f: function f() {
+            try {
+              if (!normalCompletion && it["return"] != null) it["return"]();
+            } finally {
+              if (didErr) throw err;
+            }
+          }
+        };
+      }
+
+      function _unsupportedIterableToArray(o, minLen) {
+        if (!o) return;
+        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+        var n = Object.prototype.toString.call(o).slice(8, -1);
+        if (n === "Object" && o.constructor) n = o.constructor.name;
+        if (n === "Map" || n === "Set") return Array.from(o);
+        if (
+          n === "Arguments" ||
+          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
+        )
+          return _arrayLikeToArray(o, minLen);
+      }
+
+      function _arrayLikeToArray(arr, len) {
+        if (len == null || len > arr.length) len = arr.length;
+        for (var i = 0, arr2 = new Array(len); i < len; i++) {
+          arr2[i] = arr[i];
+        }
+        return arr2;
+      }
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +155,78 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          var _iterator = _createForOfIteratorHelper(elements),
+            _step;
 
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
+          try {
+            for (_iterator.s(); !(_step = _iterator.n()).done; ) {
+              var oldTag = _step.value;
 
               if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
+                oldTags["delete"](oldTag);
+                return;
               }
             }
+          } catch (err) {
+            _iterator.e(err);
+          } finally {
+            _iterator.f();
+          }
 
-            return true;
-          });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
-        });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        var _iterator2 = _createForOfIteratorHelper(oldTags),
+          _step2;
+
+        try {
+          for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
+            var oldTag = _step2.value;
+
+            if (removeOldTags) {
+              oldTag.parentNode.removeChild(oldTag);
+            }
+
+            elements["delete"](oldTag);
+          }
+        } catch (err) {
+          _iterator2.e(err);
+        } finally {
+          _iterator2.f();
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(entry) {
+            return /*#__PURE__*/ (0,
+            _react.createElement)(entry.type, entry.props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +234,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +385,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +425,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-0ed0039a9b022173a5df.module.js"
+      href="/_next/static/chunks/main-b6ffc3be7fc6ba9dfb8d.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-4b573dcf7e6f6f186bef.js"
+      src="/_next/static/chunks/main-a2426d83751d7f32c037.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-0ed0039a9b022173a5df.module.js"
+      src="/_next/static/chunks/main-b6ffc3be7fc6ba9dfb8d.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-0ed0039a9b022173a5df.module.js"
+      href="/_next/static/chunks/main-b6ffc3be7fc6ba9dfb8d.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +93,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-4b573dcf7e6f6f186bef.js"
+      src="/_next/static/chunks/main-a2426d83751d7f32c037.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-0ed0039a9b022173a5df.module.js"
+      src="/_next/static/chunks/main-b6ffc3be7fc6ba9dfb8d.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-0ed0039a9b022173a5df.module.js"
+      href="/_next/static/chunks/main-b6ffc3be7fc6ba9dfb8d.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-4b573dcf7e6f6f186bef.js"
+      src="/_next/static/chunks/main-a2426d83751d7f32c037.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-0ed0039a9b022173a5df.module.js"
+      src="/_next/static/chunks/main-b6ffc3be7fc6ba9dfb8d.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 15.1s 15.2s ⚠️ +148ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..9ff9.js gzip 10.3 kB 10.3 kB
framework.HASH.js gzip 39 kB 39 kB
main-cc92da0..0b67.js gzip 7.35 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-e8d9293..efa9.js gzip N/A 7.71 kB N/A
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.13 kB 6.13 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-0ed168f..dule.js gzip 6.41 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-fa6ea52..dule.js gzip N/A 6.32 kB N/A
Overall change 52.3 kB 52.2 kB -99 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-ae98065..267e.js gzip 1.29 kB 1.29 kB
routerDirect..924c.js gzip 284 B 284 B
withRouter-7..c13d.js gzip 284 B 284 B
Overall change 7.69 kB 7.69 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-cb244c4..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.35 kB 5.35 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 323 B 323 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 652 B 652 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +176 B
404.html 4.22 kB 4.38 kB ⚠️ +160 B
hooks.html 3.86 kB 3.95 kB ⚠️ +86 B
index.js 1.03 MB 1.03 MB ⚠️ +176 B
link.js 1.07 MB 1.07 MB ⚠️ +176 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +176 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +176 B
Overall change 5.28 MB 5.28 MB ⚠️ +1.13 kB
Commit: 76981b1

@stefvhuynh
Copy link

stefvhuynh commented Sep 1, 2020

fyi, here is my PR for this issue: #16707

it uses a different approach that uses next-head-count along with a data- attribute. the logic involves traversing the head contents upwards and tracking the count if the element has the data- attribute.

i don't know which approach is better, per se, but figured i'd throw it in here for discussion.

@devknoll devknoll marked this pull request as ready for review September 2, 2020 18:05
@ijjk
Copy link
Member

ijjk commented Sep 2, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 13.1s 12.8s -293ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Page Load Tests Overall increase ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.417 2.297 -0.12
/ avg req/sec 1034.14 1088.33 +54.19
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.347 1.218 -0.13
/error-in-render avg req/sec 1856.14 2052.45 +196.31
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB 7.46 kB ⚠️ +360 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB 6.06 kB -101 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 972 B 1.01 kB ⚠️ +35 B
link.html gzip 978 B 1.01 kB ⚠️ +36 B
withRouter.html gzip 964 B 1 kB ⚠️ +38 B
Overall change 2.91 kB 3.02 kB ⚠️ +109 B

Diffs

Diff for main-7a454bf..8850624a9.js
@@ -42,8 +42,85 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      function _createForOfIteratorHelper(o, allowArrayLike) {
+        var it;
+        if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
+          if (
+            Array.isArray(o) ||
+            (it = _unsupportedIterableToArray(o)) ||
+            (allowArrayLike && o && typeof o.length === "number")
+          ) {
+            if (it) o = it;
+            var i = 0;
+            var F = function F() {};
+            return {
+              s: F,
+              n: function n() {
+                if (i >= o.length) return { done: true };
+                return { done: false, value: o[i++] };
+              },
+              e: function e(_e) {
+                throw _e;
+              },
+              f: F
+            };
+          }
+          throw new TypeError(
+            "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
+          );
+        }
+        var normalCompletion = true,
+          didErr = false,
+          err;
+        return {
+          s: function s() {
+            it = o[Symbol.iterator]();
+          },
+          n: function n() {
+            var step = it.next();
+            normalCompletion = step.done;
+            return step;
+          },
+          e: function e(_e2) {
+            didErr = true;
+            err = _e2;
+          },
+          f: function f() {
+            try {
+              if (!normalCompletion && it["return"] != null) it["return"]();
+            } finally {
+              if (didErr) throw err;
+            }
+          }
+        };
+      }
+
+      function _unsupportedIterableToArray(o, minLen) {
+        if (!o) return;
+        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+        var n = Object.prototype.toString.call(o).slice(8, -1);
+        if (n === "Object" && o.constructor) n = o.constructor.name;
+        if (n === "Map" || n === "Set") return Array.from(o);
+        if (
+          n === "Arguments" ||
+          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
+        )
+          return _arrayLikeToArray(o, minLen);
+      }
+
+      function _arrayLikeToArray(arr, len) {
+        if (len == null || len > arr.length) len = arr.length;
+        for (var i = 0, arr2 = new Array(len); i < len; i++) {
+          arr2[i] = arr[i];
+        }
+        return arr2;
+      }
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +155,78 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          var _iterator = _createForOfIteratorHelper(elements),
+            _step;
 
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
+          try {
+            for (_iterator.s(); !(_step = _iterator.n()).done; ) {
+              var oldTag = _step.value;
 
               if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
+                oldTags["delete"](oldTag);
+                return;
               }
             }
+          } catch (err) {
+            _iterator.e(err);
+          } finally {
+            _iterator.f();
+          }
 
-            return true;
-          });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
-        });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        var _iterator2 = _createForOfIteratorHelper(oldTags),
+          _step2;
+
+        try {
+          for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
+            var oldTag = _step2.value;
+
+            if (removeOldTags) {
+              oldTag.parentNode.removeChild(oldTag);
+            }
+
+            elements["delete"](oldTag);
+          }
+        } catch (err) {
+          _iterator2.e(err);
+        } finally {
+          _iterator2.f();
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(entry) {
+            return /*#__PURE__*/ (0,
+            _react.createElement)(entry.type, entry.props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +234,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +385,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +425,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for main-8dc84ac..f7.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,55 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
-
-        if (false) {
-        }
-
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
+            if (title !== document.title) document.title = title;
+            return;
           }
-        }
 
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
+          var newTag = reactElementToDOM(tag);
 
+          for (var oldTag of elements) {
             if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+              oldTags.delete(oldTag);
+              return;
             }
           }
 
-          return true;
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        for (var oldTag of oldTags) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements.delete(oldTag);
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(entry =>
+            /*#__PURE__*/ (0, _react.createElement)(entry.type, entry.props)
+          ),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +110,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +213,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +247,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +93,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 14.7s 14.6s -117ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-1cf35af..8d8b.js gzip N/A 7.46 kB N/A
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-629b686..dule.js gzip N/A 6.06 kB N/A
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +176 B
404.html 4.22 kB 4.38 kB ⚠️ +160 B
hooks.html 3.86 kB 3.95 kB ⚠️ +86 B
index.js 1.03 MB 1.03 MB ⚠️ +176 B
link.js 1.08 MB 1.08 MB ⚠️ +176 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +176 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +176 B
Overall change 5.29 MB 5.29 MB ⚠️ +1.13 kB
Commit: cd0f546

@ijjk
Copy link
Member

ijjk commented Sep 2, 2020

Failing test suites

Commit: cd0f546

test/integration/build-output/test/index.test.js

  • Build Output > Basic Application Output > should not deviate from snapshot
Expand output

● Build Output › Basic Application Output › should not deviate from snapshot

expect(received).toBeLessThanOrEqual(expected)

Expected: <= 0
Received:    0.10000000000000142

   96 | 
   97 |       // should be no bigger than 60.2 kb
>  98 |       expect(parseFloat(indexFirstLoad) - 60.5).toBeLessThanOrEqual(0)
      |                                                 ^
   99 |       expect(indexFirstLoad.endsWith('kB')).toBe(true)
  100 | 
  101 |       expect(parseFloat(err404Size) - 3.5).toBeLessThanOrEqual(0)

  at Object.<anonymous> (integration/build-output/test/index.test.js:98:49)

@ijjk
Copy link
Member

ijjk commented Sep 2, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 13.2s 12.9s -214ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Page Load Tests Overall decrease ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.369 2.32 -0.05
/ avg req/sec 1055.36 1077.38 +22.02
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.28 1.295 ⚠️ +0.01
/error-in-render avg req/sec 1953.6 1930.5 ⚠️ -23.1
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB 7.46 kB ⚠️ +360 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB 6.06 kB -101 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 972 B 1.01 kB ⚠️ +35 B
link.html gzip 978 B 1.01 kB ⚠️ +36 B
withRouter.html gzip 964 B 1 kB ⚠️ +38 B
Overall change 2.91 kB 3.02 kB ⚠️ +109 B

Diffs

Diff for main-7a454bf..8850624a9.js
@@ -42,8 +42,85 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      function _createForOfIteratorHelper(o, allowArrayLike) {
+        var it;
+        if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
+          if (
+            Array.isArray(o) ||
+            (it = _unsupportedIterableToArray(o)) ||
+            (allowArrayLike && o && typeof o.length === "number")
+          ) {
+            if (it) o = it;
+            var i = 0;
+            var F = function F() {};
+            return {
+              s: F,
+              n: function n() {
+                if (i >= o.length) return { done: true };
+                return { done: false, value: o[i++] };
+              },
+              e: function e(_e) {
+                throw _e;
+              },
+              f: F
+            };
+          }
+          throw new TypeError(
+            "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
+          );
+        }
+        var normalCompletion = true,
+          didErr = false,
+          err;
+        return {
+          s: function s() {
+            it = o[Symbol.iterator]();
+          },
+          n: function n() {
+            var step = it.next();
+            normalCompletion = step.done;
+            return step;
+          },
+          e: function e(_e2) {
+            didErr = true;
+            err = _e2;
+          },
+          f: function f() {
+            try {
+              if (!normalCompletion && it["return"] != null) it["return"]();
+            } finally {
+              if (didErr) throw err;
+            }
+          }
+        };
+      }
+
+      function _unsupportedIterableToArray(o, minLen) {
+        if (!o) return;
+        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+        var n = Object.prototype.toString.call(o).slice(8, -1);
+        if (n === "Object" && o.constructor) n = o.constructor.name;
+        if (n === "Map" || n === "Set") return Array.from(o);
+        if (
+          n === "Arguments" ||
+          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
+        )
+          return _arrayLikeToArray(o, minLen);
+      }
+
+      function _arrayLikeToArray(arr, len) {
+        if (len == null || len > arr.length) len = arr.length;
+        for (var i = 0, arr2 = new Array(len); i < len; i++) {
+          arr2[i] = arr[i];
+        }
+        return arr2;
+      }
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +155,78 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          var _iterator = _createForOfIteratorHelper(elements),
+            _step;
 
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
+          try {
+            for (_iterator.s(); !(_step = _iterator.n()).done; ) {
+              var oldTag = _step.value;
 
               if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
+                oldTags["delete"](oldTag);
+                return;
               }
             }
+          } catch (err) {
+            _iterator.e(err);
+          } finally {
+            _iterator.f();
+          }
 
-            return true;
-          });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
-        });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        var _iterator2 = _createForOfIteratorHelper(oldTags),
+          _step2;
+
+        try {
+          for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
+            var oldTag = _step2.value;
+
+            if (removeOldTags) {
+              oldTag.parentNode.removeChild(oldTag);
+            }
+
+            elements["delete"](oldTag);
+          }
+        } catch (err) {
+          _iterator2.e(err);
+        } finally {
+          _iterator2.f();
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(entry) {
+            return /*#__PURE__*/ (0,
+            _react.createElement)(entry.type, entry.props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +234,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +385,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +425,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for main-8dc84ac..f7.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,55 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
-
-        if (false) {
-        }
-
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
+            if (title !== document.title) document.title = title;
+            return;
           }
-        }
 
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
+          var newTag = reactElementToDOM(tag);
 
+          for (var oldTag of elements) {
             if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+              oldTags.delete(oldTag);
+              return;
             }
           }
 
-          return true;
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        for (var oldTag of oldTags) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements.delete(oldTag);
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(entry =>
+            /*#__PURE__*/ (0, _react.createElement)(entry.type, entry.props)
+          ),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +110,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +213,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +247,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +93,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 14.8s 14.9s ⚠️ +160ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-1cf35af..8d8b.js gzip N/A 7.46 kB N/A
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-629b686..dule.js gzip N/A 6.06 kB N/A
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +176 B
404.html 4.22 kB 4.38 kB ⚠️ +160 B
hooks.html 3.86 kB 3.95 kB ⚠️ +86 B
index.js 1.03 MB 1.03 MB ⚠️ +176 B
link.js 1.08 MB 1.08 MB ⚠️ +176 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +176 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +176 B
Overall change 5.29 MB 5.29 MB ⚠️ +1.13 kB
Commit: 4d42ded

@ijjk
Copy link
Member

ijjk commented Sep 2, 2020

Failing test suites

Commit: 4d42ded

test/integration/build-output/test/index.test.js

  • Build Output > Basic Application Output > should not deviate from snapshot
Expand output

● Build Output › Basic Application Output › should not deviate from snapshot

expect(received).toBeLessThanOrEqual(expected)

Expected: <= 0
Received:    0.10000000000000142

   96 | 
   97 |       // should be no bigger than 60.5 kb
>  98 |       expect(parseFloat(indexFirstLoad) - 60.5).toBeLessThanOrEqual(0)
      |                                                 ^
   99 |       expect(indexFirstLoad.endsWith('kB')).toBe(true)
  100 | 
  101 |       expect(parseFloat(err404Size) - 3.5).toBeLessThanOrEqual(0)

  at Object.<anonymous> (integration/build-output/test/index.test.js:98:49)

@ijjk
Copy link
Member

ijjk commented Sep 2, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 11.6s 12.1s ⚠️ +464ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Page Load Tests Overall increase ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.182 2.308 ⚠️ +0.13
/ avg req/sec 1145.83 1083.12 ⚠️ -62.71
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.384 1.335 -0.05
/error-in-render avg req/sec 1806.49 1873.23 +66.74
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB 7.46 kB ⚠️ +360 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB 6.06 kB -101 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 972 B 1.01 kB ⚠️ +35 B
link.html gzip 978 B 1.01 kB ⚠️ +36 B
withRouter.html gzip 964 B 1 kB ⚠️ +38 B
Overall change 2.91 kB 3.02 kB ⚠️ +109 B

Diffs

Diff for main-7a454bf..8850624a9.js
@@ -42,8 +42,85 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      function _createForOfIteratorHelper(o, allowArrayLike) {
+        var it;
+        if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
+          if (
+            Array.isArray(o) ||
+            (it = _unsupportedIterableToArray(o)) ||
+            (allowArrayLike && o && typeof o.length === "number")
+          ) {
+            if (it) o = it;
+            var i = 0;
+            var F = function F() {};
+            return {
+              s: F,
+              n: function n() {
+                if (i >= o.length) return { done: true };
+                return { done: false, value: o[i++] };
+              },
+              e: function e(_e) {
+                throw _e;
+              },
+              f: F
+            };
+          }
+          throw new TypeError(
+            "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
+          );
+        }
+        var normalCompletion = true,
+          didErr = false,
+          err;
+        return {
+          s: function s() {
+            it = o[Symbol.iterator]();
+          },
+          n: function n() {
+            var step = it.next();
+            normalCompletion = step.done;
+            return step;
+          },
+          e: function e(_e2) {
+            didErr = true;
+            err = _e2;
+          },
+          f: function f() {
+            try {
+              if (!normalCompletion && it["return"] != null) it["return"]();
+            } finally {
+              if (didErr) throw err;
+            }
+          }
+        };
+      }
+
+      function _unsupportedIterableToArray(o, minLen) {
+        if (!o) return;
+        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+        var n = Object.prototype.toString.call(o).slice(8, -1);
+        if (n === "Object" && o.constructor) n = o.constructor.name;
+        if (n === "Map" || n === "Set") return Array.from(o);
+        if (
+          n === "Arguments" ||
+          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
+        )
+          return _arrayLikeToArray(o, minLen);
+      }
+
+      function _arrayLikeToArray(arr, len) {
+        if (len == null || len > arr.length) len = arr.length;
+        for (var i = 0, arr2 = new Array(len); i < len; i++) {
+          arr2[i] = arr[i];
+        }
+        return arr2;
+      }
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +155,78 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          var _iterator = _createForOfIteratorHelper(elements),
+            _step;
 
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
+          try {
+            for (_iterator.s(); !(_step = _iterator.n()).done; ) {
+              var oldTag = _step.value;
 
               if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
+                oldTags["delete"](oldTag);
+                return;
               }
             }
+          } catch (err) {
+            _iterator.e(err);
+          } finally {
+            _iterator.f();
+          }
 
-            return true;
-          });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
-        });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        var _iterator2 = _createForOfIteratorHelper(oldTags),
+          _step2;
+
+        try {
+          for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
+            var oldTag = _step2.value;
+
+            if (removeOldTags) {
+              oldTag.parentNode.removeChild(oldTag);
+            }
+
+            elements["delete"](oldTag);
+          }
+        } catch (err) {
+          _iterator2.e(err);
+        } finally {
+          _iterator2.f();
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(entry) {
+            return /*#__PURE__*/ (0,
+            _react.createElement)(entry.type, entry.props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +234,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +385,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +425,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for main-8dc84ac..f7.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,55 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
-
-        if (false) {
-        }
-
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
+            if (title !== document.title) document.title = title;
+            return;
           }
-        }
 
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
+          var newTag = reactElementToDOM(tag);
 
+          for (var oldTag of elements) {
             if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+              oldTags.delete(oldTag);
+              return;
             }
           }
 
-          return true;
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        for (var oldTag of oldTags) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements.delete(oldTag);
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(entry =>
+            /*#__PURE__*/ (0, _react.createElement)(entry.type, entry.props)
+          ),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +110,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +213,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +247,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +93,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 13.2s 13.2s -34ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-1cf35af..8d8b.js gzip N/A 7.46 kB N/A
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-629b686..dule.js gzip N/A 6.06 kB N/A
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +176 B
404.html 4.22 kB 4.38 kB ⚠️ +160 B
hooks.html 3.86 kB 3.95 kB ⚠️ +86 B
index.js 1.03 MB 1.03 MB ⚠️ +176 B
link.js 1.08 MB 1.08 MB ⚠️ +176 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +176 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +176 B
Overall change 5.29 MB 5.29 MB ⚠️ +1.13 kB
Commit: 4f2400c

@ijjk
Copy link
Member

ijjk commented Sep 2, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 12.8s 12.8s ⚠️ +38ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Page Load Tests Overall decrease ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.345 2.354 ⚠️ +0.01
/ avg req/sec 1065.93 1061.82 ⚠️ -4.11
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.285 1.293 ⚠️ +0.01
/error-in-render avg req/sec 1946.23 1933.3 ⚠️ -12.93
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB 7.46 kB ⚠️ +360 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB 6.06 kB -101 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 972 B 1.01 kB ⚠️ +35 B
link.html gzip 978 B 1.01 kB ⚠️ +36 B
withRouter.html gzip 964 B 1 kB ⚠️ +38 B
Overall change 2.91 kB 3.02 kB ⚠️ +109 B

Diffs

Diff for main-7a454bf..8850624a9.js
@@ -42,8 +42,85 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      function _createForOfIteratorHelper(o, allowArrayLike) {
+        var it;
+        if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
+          if (
+            Array.isArray(o) ||
+            (it = _unsupportedIterableToArray(o)) ||
+            (allowArrayLike && o && typeof o.length === "number")
+          ) {
+            if (it) o = it;
+            var i = 0;
+            var F = function F() {};
+            return {
+              s: F,
+              n: function n() {
+                if (i >= o.length) return { done: true };
+                return { done: false, value: o[i++] };
+              },
+              e: function e(_e) {
+                throw _e;
+              },
+              f: F
+            };
+          }
+          throw new TypeError(
+            "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
+          );
+        }
+        var normalCompletion = true,
+          didErr = false,
+          err;
+        return {
+          s: function s() {
+            it = o[Symbol.iterator]();
+          },
+          n: function n() {
+            var step = it.next();
+            normalCompletion = step.done;
+            return step;
+          },
+          e: function e(_e2) {
+            didErr = true;
+            err = _e2;
+          },
+          f: function f() {
+            try {
+              if (!normalCompletion && it["return"] != null) it["return"]();
+            } finally {
+              if (didErr) throw err;
+            }
+          }
+        };
+      }
+
+      function _unsupportedIterableToArray(o, minLen) {
+        if (!o) return;
+        if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+        var n = Object.prototype.toString.call(o).slice(8, -1);
+        if (n === "Object" && o.constructor) n = o.constructor.name;
+        if (n === "Map" || n === "Set") return Array.from(o);
+        if (
+          n === "Arguments" ||
+          /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
+        )
+          return _arrayLikeToArray(o, minLen);
+      }
+
+      function _arrayLikeToArray(arr, len) {
+        if (len == null || len > arr.length) len = arr.length;
+        for (var i = 0, arr2 = new Array(len); i < len; i++) {
+          arr2[i] = arr[i];
+        }
+        return arr2;
+      }
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +155,78 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          var _iterator = _createForOfIteratorHelper(elements),
+            _step;
 
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
+          try {
+            for (_iterator.s(); !(_step = _iterator.n()).done; ) {
+              var oldTag = _step.value;
 
               if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
+                oldTags["delete"](oldTag);
+                return;
               }
             }
+          } catch (err) {
+            _iterator.e(err);
+          } finally {
+            _iterator.f();
+          }
 
-            return true;
-          });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
-        });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        var _iterator2 = _createForOfIteratorHelper(oldTags),
+          _step2;
+
+        try {
+          for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
+            var oldTag = _step2.value;
+
+            if (removeOldTags) {
+              oldTag.parentNode.removeChild(oldTag);
+            }
+
+            elements["delete"](oldTag);
+          }
+        } catch (err) {
+          _iterator2.e(err);
+        } finally {
+          _iterator2.f();
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(entry) {
+            return /*#__PURE__*/ (0,
+            _react.createElement)(entry.type, entry.props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +234,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +385,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +425,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for main-8dc84ac..f7.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,55 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
-
-        if (false) {
-        }
-
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
+            if (title !== document.title) document.title = title;
+            return;
           }
-        }
 
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
+          var newTag = reactElementToDOM(tag);
 
+          for (var oldTag of elements) {
             if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+              oldTags.delete(oldTag);
+              return;
             }
           }
 
-          return true;
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
+
+        for (var oldTag of oldTags) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements.delete(oldTag);
+        }
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(entry =>
+            /*#__PURE__*/ (0, _react.createElement)(entry.type, entry.props)
+          ),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +110,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +213,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +247,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +93,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      href="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,14 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          { "type": "meta", "props": { "charSet": "utf-8" } },
+          {
+            "type": "meta",
+            "props": { "name": "viewport", "content": "width=device-width" }
+          }
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +88,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-7a454bf2fbe8850624a9.js"
+      src="/_next/static/chunks/main-faa78a6e09e9e9933153.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-8dc84ac93a9cb82365f7.module.js"
+      src="/_next/static/chunks/main-76023163c15ee13f4725.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 14.9s 14.4s -479ms
nodeModulesSize 55.9 MB 55.9 MB -600 B
Client Bundles (main, webpack, commons) Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..01b7.js gzip 10.5 kB 10.5 kB
framework.HASH.js gzip 39 kB 39 kB
main-f69b135..640b.js gzip 7.1 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-1cf35af..8d8b.js gzip N/A 7.46 kB N/A
Overall change 57.3 kB 57.7 kB ⚠️ +360 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.39 kB 6.39 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-ed2fed0..dule.js gzip 6.16 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-629b686..dule.js gzip N/A 6.06 kB N/A
Overall change 52.3 kB 52.2 kB -101 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +176 B
404.html 4.22 kB 4.38 kB ⚠️ +160 B
hooks.html 3.86 kB 3.95 kB ⚠️ +86 B
index.js 1.03 MB 1.03 MB ⚠️ +176 B
link.js 1.08 MB 1.08 MB ⚠️ +176 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +176 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +176 B
Overall change 5.29 MB 5.29 MB ⚠️ +1.13 kB
Commit: 532466e

@ijjk
Copy link
Member

ijjk commented Sep 8, 2020

Failing test suites

Commit: 443201e

test/integration/amphtml-fragment-style/test/index.test.js

  • AMP Fragment Styles > adds styles from fragment in AMP mode correctly
Expand output

● AMP Fragment Styles › adds styles from fragment in AMP mode correctly

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● Test suite failed to run

TypeError: Cannot read property '__app' of undefined

  309 | 
  310 | export async function stopApp(server) {
> 311 |   if (server.__app) {
      |              ^
  312 |     await server.__app.close()
  313 |   }
  314 |   await promiseCall(server, 'close')

  at stopApp (lib/next-test-utils.js:311:14)
  at integration/amphtml-fragment-style/test/index.test.js:32:18

test/integration/app-functional/test/index.test.js

  • Document and App > should not have any missing key warnings
Expand output

● Document and App › should not have any missing key warnings

expect(received).toMatch(expected)

Expected pattern: /<div>Hello World!!!<\/div>/
Received string:  "Internal Server Error"

  28 |   it('should not have any missing key warnings', async () => {
  29 |     const html = await renderViaHTTP(context.appPort, '/')
> 30 |     expect(html).toMatch(/<div>Hello World!!!<\/div>/)
     |                  ^
  31 |   })
  32 | })
  33 | 

  at Object.<anonymous> (integration/app-functional/test/index.test.js:30:18)

test/integration/absolute-assetprefix/test/index.test.js

  • absolute assetPrefix with path prefix > should not fetch static data from a CDN
  • absolute assetPrefix with path prefix > should fetch from cache correctly
  • absolute assetPrefix with path prefix > should work with getStaticPaths prerendered
  • absolute assetPrefix with path prefix > should work with getStaticPaths fallback
  • absolute assetPrefix with path prefix > should work with getServerSideProps
Expand output

● absolute assetPrefix with path prefix › should not fetch static data from a CDN

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should fetch from cache correctly

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should work with getStaticPaths prerendered

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should work with getStaticPaths fallback

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should work with getServerSideProps

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● Test suite failed to run

TypeError: Cannot read property 'pid' of undefined

  275 | export async function killApp(instance) {
  276 |   await new Promise((resolve, reject) => {
> 277 |     treeKill(instance.pid, (err) => {
      |                       ^
  278 |       if (err) {
  279 |         if (
  280 |           process.platform === 'win32' &&

  at lib/next-test-utils.js:277:23
  at killApp (lib/next-test-utils.js:276:9)
  at integration/absolute-assetprefix/test/index.test.js:65:18

test/integration/catches-missing-getStaticProps/test/index.test.js

  • Catches Missing getStaticProps > should catch it in dev mode
Expand output

● Catches Missing getStaticProps › should catch it in dev mode

expect(received).toMatch(expected)

Expected pattern: /getStaticPaths was added without a getStaticProps in/
Received string:  "Internal Server Error"

  26 |     await killApp(app)
  27 | 
> 28 |     expect(html).toMatch(errorRegex)
     |                  ^
  29 |   })
  30 | 
  31 |   it('should catch it in server build mode', async () => {

  at Object.<anonymous> (integration/catches-missing-getStaticProps/test/index.test.js:28:18)

test/integration/amphtml-custom-optimizer/test/index.test.js

  • AMP Custom Optimizer > should build and start for static page
  • AMP Custom Optimizer > should build and start for dynamic page
Expand output

● AMP Custom Optimizer › should build and start for static page

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● AMP Custom Optimizer › should build and start for dynamic page

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

test/integration/404-page-app/test/index.test.js

  • 404 Page Support with _app > dev mode > should not show pages/404 GIP error if _app has GIP
  • 404 Page Support with _app > production mode > should not output static 404 if _app has getInitialProps
  • 404 Page Support with _app > production mode > should still use 404 page
Expand output

● 404 Page Support with _app › production mode › should not output static 404 if _app has getInitialProps

JavascriptError: javascript error: __NEXT_DATA__ is not defined
  (Session info: headless chrome=85.0.4183.83)

  42 |     it('should not output static 404 if _app has getInitialProps', async () => {
  43 |       const browser = await webdriver(appPort, '/404')
> 44 |       const isAutoExported = await browser.eval('__NEXT_DATA__.autoExport')
     |                              ^
  45 |       expect(isAutoExported).toBe(null)
  46 |     })
  47 | 

  at Object.throwDecodedError (../node_modules/selenium-webdriver/lib/error.js:550:15)
  at parseHttpResponse (../node_modules/selenium-webdriver/lib/http.js:565:13)
  at Executor.execute (../node_modules/selenium-webdriver/lib/http.js:491:26)
  at thenableWebDriverProxy.execute (../node_modules/selenium-webdriver/lib/webdriver.js:700:17)
  at Object.<anonymous> (integration/404-page-app/test/index.test.js:44:30)

● 404 Page Support with _app › production mode › should still use 404 page

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

Expected: 404
Received: 500

  55 |     it('should still use 404 page', async () => {
  56 |       const res = await fetchViaHTTP(appPort, '/abc')
> 57 |       expect(res.status).toBe(404)
     |                          ^
  58 |       const $ = cheerio.load(await res.text())
  59 |       expect($('#404-title').text()).toBe('Hi There')
  60 |     })

  at Object.<anonymous> (integration/404-page-app/test/index.test.js:57:26)

● 404 Page Support with _app › dev mode › should not show pages/404 GIP error if _app has GIP

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

Expected: 404
Received: 500

  80 |     it('should not show pages/404 GIP error if _app has GIP', async () => {
  81 |       const res = await fetchViaHTTP(appPort, '/abc')
> 82 |       expect(res.status).toBe(404)
     |                          ^
  83 |       const $ = cheerio.load(await res.text())
  84 |       expect($('#404-title').text()).toBe('Hi There')
  85 |       expect(stderr).not.toMatch(gip404Err)

  at Object.<anonymous> (integration/404-page-app/test/index.test.js:82:26)

@ijjk
Copy link
Member

ijjk commented Sep 8, 2020

Failing test suites

Commit: 8631f99

test/integration/amphtml-fragment-style/test/index.test.js

  • AMP Fragment Styles > adds styles from fragment in AMP mode correctly
Expand output

● AMP Fragment Styles › adds styles from fragment in AMP mode correctly

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● Test suite failed to run

TypeError: Cannot read property '__app' of undefined

  309 | 
  310 | export async function stopApp(server) {
> 311 |   if (server.__app) {
      |              ^
  312 |     await server.__app.close()
  313 |   }
  314 |   await promiseCall(server, 'close')

  at stopApp (lib/next-test-utils.js:311:14)
  at integration/amphtml-fragment-style/test/index.test.js:32:18

test/integration/compression/test/index.test.js

  • Compression > should compress responses by default
Expand output

● Compression › should compress responses by default

expect(received).toMatch(expected)

Matcher error: received value must be a string

Received has value: null

  26 |     const res = await fetchViaHTTP(context.appPort, '/')
  27 | 
> 28 |     expect(res.headers.get('content-encoding')).toMatch(/gzip/)
     |                                                 ^
  29 |   })
  30 | })
  31 | 

  at Object.<anonymous> (integration/compression/test/index.test.js:28:49)

test/integration/amphtml-custom-validator/test/index.test.js

  • AMP Custom Validator > should build and start successfully
  • AMP Custom Validator > should run in dev mode successfully
Expand output

● AMP Custom Validator › should build and start successfully

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● AMP Custom Validator › should run in dev mode successfully

expect(received).toContain(expected) // indexOf

Expected substring: "Hello from AMP"
Received string:    "Internal Server Error"

  45 | 
  46 |     expect(stderr).not.toContain('error')
> 47 |     expect(html).toContain('Hello from AMP')
     |                  ^
  48 |   })
  49 | })
  50 | 

  at Object.<anonymous> (integration/amphtml-custom-validator/test/index.test.js:47:18)

test/integration/amphtml-custom-optimizer/test/index.test.js

  • AMP Custom Optimizer > should build and start for static page
  • AMP Custom Optimizer > should build and start for dynamic page
Expand output

● AMP Custom Optimizer › should build and start for static page

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● AMP Custom Optimizer › should build and start for dynamic page

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

test/integration/absolute-assetprefix/test/index.test.js

  • absolute assetPrefix with path prefix > should not fetch static data from a CDN
  • absolute assetPrefix with path prefix > should fetch from cache correctly
  • absolute assetPrefix with path prefix > should work with getStaticPaths prerendered
  • absolute assetPrefix with path prefix > should work with getStaticPaths fallback
  • absolute assetPrefix with path prefix > should work with getServerSideProps
Expand output

● absolute assetPrefix with path prefix › should not fetch static data from a CDN

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should fetch from cache correctly

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should work with getStaticPaths prerendered

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should work with getStaticPaths fallback

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● absolute assetPrefix with path prefix › should work with getServerSideProps

command failed with code 1

  132 |         code !== 0
  133 |       ) {
> 134 |         return reject(new Error(`command failed with code ${code}`))
      |                       ^
  135 |       }
  136 | 
  137 |       resolve({

  at ChildProcess.<anonymous> (lib/next-test-utils.js:134:23)

● Test suite failed to run

TypeError: Cannot read property 'pid' of undefined

  275 | export async function killApp(instance) {
  276 |   await new Promise((resolve, reject) => {
> 277 |     treeKill(instance.pid, (err) => {
      |                       ^
  278 |       if (err) {
  279 |         if (
  280 |           process.platform === 'win32' &&

  at lib/next-test-utils.js:277:23
  at killApp (lib/next-test-utils.js:276:9)
  at integration/absolute-assetprefix/test/index.test.js:65:18

test/integration/404-page-app/test/index.test.js

  • 404 Page Support with _app > dev mode > should not show pages/404 GIP error if _app has GIP
  • 404 Page Support with _app > production mode > should not output static 404 if _app has getInitialProps
  • 404 Page Support with _app > production mode > should still use 404 page
Expand output

● 404 Page Support with _app › production mode › should not output static 404 if _app has getInitialProps

JavascriptError: javascript error: __NEXT_DATA__ is not defined
  (Session info: headless chrome=85.0.4183.83)

  42 |     it('should not output static 404 if _app has getInitialProps', async () => {
  43 |       const browser = await webdriver(appPort, '/404')
> 44 |       const isAutoExported = await browser.eval('__NEXT_DATA__.autoExport')
     |                              ^
  45 |       expect(isAutoExported).toBe(null)
  46 |     })
  47 | 

  at Object.throwDecodedError (../node_modules/selenium-webdriver/lib/error.js:550:15)
  at parseHttpResponse (../node_modules/selenium-webdriver/lib/http.js:565:13)
  at Executor.execute (../node_modules/selenium-webdriver/lib/http.js:491:26)
  at thenableWebDriverProxy.execute (../node_modules/selenium-webdriver/lib/webdriver.js:700:17)
  at Object.<anonymous> (integration/404-page-app/test/index.test.js:44:30)

● 404 Page Support with _app › production mode › should still use 404 page

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

Expected: 404
Received: 500

  55 |     it('should still use 404 page', async () => {
  56 |       const res = await fetchViaHTTP(appPort, '/abc')
> 57 |       expect(res.status).toBe(404)
     |                          ^
  58 |       const $ = cheerio.load(await res.text())
  59 |       expect($('#404-title').text()).toBe('Hi There')
  60 |     })

  at Object.<anonymous> (integration/404-page-app/test/index.test.js:57:26)

● 404 Page Support with _app › dev mode › should not show pages/404 GIP error if _app has GIP

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

Expected: 404
Received: 500

  80 |     it('should not show pages/404 GIP error if _app has GIP', async () => {
  81 |       const res = await fetchViaHTTP(appPort, '/abc')
> 82 |       expect(res.status).toBe(404)
     |                          ^
  83 |       const $ = cheerio.load(await res.text())
  84 |       expect($('#404-title').text()).toBe('Hi There')
  85 |       expect(stderr).not.toMatch(gip404Err)

  at Object.<anonymous> (integration/404-page-app/test/index.test.js:82:26)

@ijjk
Copy link
Member

ijjk commented Sep 8, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 13s 13.1s ⚠️ +73ms
nodeModulesSize 56.7 MB 56.7 MB -640 B
Page Load Tests Overall increase ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.339 2.356 ⚠️ +0.02
/ avg req/sec 1069.02 1060.98 ⚠️ -8.04
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.321 1.307 -0.01
/error-in-render avg req/sec 1892.97 1913.23 +20.26
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..a2cc.js gzip 10.8 kB 10.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-4d826ad..023b.js gzip 7.08 kB 6.95 kB -131 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.6 kB 57.4 kB -131 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.62 kB 6.62 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-4dbc74f..dule.js gzip 6.14 kB 6.02 kB -124 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.5 kB 52.4 kB -124 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 970 B 1 kB ⚠️ +33 B
link.html gzip 977 B 1.01 kB ⚠️ +32 B
withRouter.html gzip 963 B 994 B ⚠️ +31 B
Overall change 2.91 kB 3.01 kB ⚠️ +96 B

Diffs

Diff for main-3510944..db9e3ffb3.js
@@ -42,8 +42,13 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      var _slicedToArray = __webpack_require__("J4zp");
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +83,56 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
-
-        if (false) {
-        }
-
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
+            if (title !== document.title) document.title = title;
+            return;
           }
-        }
-
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
 
-              if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
-              }
+          var newTag = reactElementToDOM(tag);
+          elements.forEach(function(oldTag) {
+            if (oldTag.isEqualNode(newTag)) {
+              oldTags["delete"](oldTag);
+              return;
             }
-
-            return true;
           });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+        oldTags.forEach(function(oldTag) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements["delete"](oldTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(_ref2) {
+            var _ref3 = _slicedToArray(_ref2, 2),
+              type = _ref3[0],
+              props = _ref3[1];
+
+            return /*#__PURE__*/ (0, _react.createElement)(type, props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +140,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +291,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +331,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for main-50ad093..33.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,53 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
-
-        if (false) {
-        }
-
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
+            if (title !== document.title) document.title = title;
+            return;
           }
-        }
-
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
 
+          var newTag = reactElementToDOM(tag);
+          elements.forEach(oldTag => {
             if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+              oldTags.delete(oldTag);
+              return;
             }
+          });
+          elements.add(newTag);
+          headEl.appendChild(newTag);
+        });
+        oldTags.forEach(oldTag => {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
           }
 
-          return true;
+          elements.delete(oldTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(_ref2 => {
+            var [type, props] = _ref2;
+            return /*#__PURE__*/ (0, _react.createElement)(type, props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +108,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +211,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +245,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-90e70a929ab023ad0559.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +85,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-ba72dc6c21b5490cdaa7.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-90e70a929ab023ad0559.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-90e70a929ab023ad0559.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +90,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-ba72dc6c21b5490cdaa7.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-90e70a929ab023ad0559.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-90e70a929ab023ad0559.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +85,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-ba72dc6c21b5490cdaa7.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-90e70a929ab023ad0559.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 14.7s 14.6s -67ms
nodeModulesSize 56.7 MB 56.7 MB -640 B
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..a2cc.js gzip 10.8 kB 10.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-4d826ad..023b.js gzip 7.08 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-3f8b16f..fcf9.js gzip N/A 6.95 kB N/A
Overall change 57.6 kB 57.4 kB -131 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.62 kB 6.62 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-4dbc74f..dule.js gzip 6.14 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-9501de2..dule.js gzip N/A 6.02 kB N/A
Overall change 52.5 kB 52.4 kB -124 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +174 B
404.html 4.22 kB 4.34 kB ⚠️ +115 B
hooks.html 3.86 kB 3.92 kB ⚠️ +56 B
index.js 1.03 MB 1.03 MB ⚠️ +174 B
link.js 1.08 MB 1.08 MB ⚠️ +174 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +174 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +174 B
Overall change 5.3 MB 5.31 MB ⚠️ +1.04 kB
Commit: 5b6fc47

@ijjk
Copy link
Member

ijjk commented Sep 8, 2020

Failing test suites

Commit: 5b6fc47

test/integration/production/test/index.test.js

  • Production Usage > should not clear custom performance marks
Expand output

● Production Usage › should not clear custom performance marks

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

Expected: true
Received: false

  815 |         }).length === 1`
  816 |       )
> 817 |       expect(customMarkFound).toBe(true)
      |                               ^
  818 |     } finally {
  819 |       if (browser) {
  820 |         await browser.close()

  at Object.<anonymous> (integration/production/test/index.test.js:817:31)

@ijjk
Copy link
Member

ijjk commented Sep 8, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 12.2s 12.7s ⚠️ +460ms
nodeModulesSize 56.7 MB 56.7 MB -134 B
Page Load Tests Overall increase ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.243 2.27 ⚠️ +0.03
/ avg req/sec 1114.38 1101.52 ⚠️ -12.86
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.197 1.187 -0.01
/error-in-render avg req/sec 2088.31 2105.28 +16.97
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..a2cc.js gzip 10.8 kB 10.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-4d826ad..023b.js gzip 7.08 kB 6.99 kB -88 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.6 kB 57.5 kB -88 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.62 kB 6.62 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-4dbc74f..dule.js gzip 6.14 kB 6.06 kB -83 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.5 kB 52.4 kB -83 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 970 B 1 kB ⚠️ +35 B
link.html gzip 977 B 1.01 kB ⚠️ +33 B
withRouter.html gzip 963 B 996 B ⚠️ +33 B
Overall change 2.91 kB 3.01 kB ⚠️ +101 B

Diffs

Diff for main-3510944..db9e3ffb3.js
@@ -42,8 +42,13 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      var _slicedToArray = __webpack_require__("J4zp");
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +83,68 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
+          var elementIter = elements.values();
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          while (true) {
+            // Note: We don't use for-of here to avoid needing to polyfill it.
+            var _elementIter$next = elementIter.next(),
+              done = _elementIter$next.done,
+              value = _elementIter$next.value;
 
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
+            if (value.isEqualNode(newTag)) {
+              oldTags["delete"](value);
+              return;
+            }
 
-              if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
-              }
+            if (done) {
+              break;
             }
+          }
 
-            return true;
-          });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+        oldTags.forEach(function(oldTag) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements["delete"](oldTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(_ref2) {
+            var _ref3 = _slicedToArray(_ref2, 2),
+              type = _ref3[0],
+              props = _ref3[1];
+
+            return /*#__PURE__*/ (0, _react.createElement)(type, props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +152,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +303,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +343,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for main-50ad093..33.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,63 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
+          var elementIter = elements.values();
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          while (true) {
+            // Note: We don't use for-of here to avoid needing to polyfill it.
+            var { done, value } = elementIter.next();
 
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
+            if (value.isEqualNode(newTag)) {
+              oldTags.delete(value);
+              return;
+            }
 
-            if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+            if (done) {
+              break;
             }
           }
 
-          return true;
+          elements.add(newTag);
+          headEl.appendChild(newTag);
+        });
+        oldTags.forEach(oldTag => {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements.delete(oldTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(_ref2 => {
+            var [type, props] = _ref2;
+            return /*#__PURE__*/ (0, _react.createElement)(type, props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +118,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +221,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +255,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-a7f4d67c273dd2c89a36.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +85,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-bc5e7bda814089506bc8.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-a7f4d67c273dd2c89a36.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-a7f4d67c273dd2c89a36.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +90,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-bc5e7bda814089506bc8.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-a7f4d67c273dd2c89a36.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-a7f4d67c273dd2c89a36.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +85,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-bc5e7bda814089506bc8.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-a7f4d67c273dd2c89a36.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 13.8s 13.7s -64ms
nodeModulesSize 56.7 MB 56.7 MB -134 B
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..a2cc.js gzip 10.8 kB 10.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-4d826ad..023b.js gzip 7.08 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-5739296..1359.js gzip N/A 6.99 kB N/A
Overall change 57.6 kB 57.5 kB -88 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.62 kB 6.62 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-4dbc74f..dule.js gzip 6.14 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-c854a4a..dule.js gzip N/A 6.06 kB N/A
Overall change 52.5 kB 52.4 kB -83 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +174 B
404.html 4.22 kB 4.34 kB ⚠️ +115 B
hooks.html 3.86 kB 3.92 kB ⚠️ +56 B
index.js 1.03 MB 1.03 MB ⚠️ +174 B
link.js 1.08 MB 1.08 MB ⚠️ +174 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +174 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +174 B
Overall change 5.3 MB 5.31 MB ⚠️ +1.04 kB
Commit: 339174d

@ijjk
Copy link
Member

ijjk commented Sep 8, 2020

Failing test suites

Commit: 339174d

test/integration/basic/test/index.test.js

  • Basic Features > Dynamic import > default behavior > should render the component Head content
Expand output

● Basic Features › Dynamic import › default behavior › should render the component Head content

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

Expected: "200px"
Received: "18px"

  86 |             .elementByCss('.dynamic-style')
  87 |             .getComputedCss('height')
> 88 |           expect(height).toBe('200px')
     |                          ^
  89 |           expect(backgroundColor).toBe('rgba(0, 128, 0, 1)')
  90 |         } finally {
  91 |           if (browser) {

  at Object.<anonymous> (integration/basic/test/dynamic.js:88:26)

test/integration/client-navigation/test/index.test.js

  • Client Navigation > updating head while client routing > should update head during client routing
  • Client Navigation > updating head while client routing > should update title during client routing
Expand output

● Client Navigation › updating head while client routing › should update head during client routing

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

Expected: "Head Two"
Received: "Head One"

  1207 |             .elementByCss('meta[name="description"]')
  1208 |             .getAttribute('content')
> 1209 |         ).toBe('Head Two')
       |           ^
  1210 | 
  1211 |         await browser
  1212 |           .elementByCss('#to-head-1')

  at Object.<anonymous> (integration/client-navigation/test/index.test.js:1209:11)
      at runMicrotasks (<anonymous>)

● Client Navigation › updating head while client routing › should update title during client routing

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

Expected: "this is head-2"
Received: "this is head-1"

  1235 |           .click()
  1236 |           .waitForElementByCss('#head-2', 3000)
> 1237 |         expect(await browser.eval('document.title')).toBe('this is head-2')
       |                                                      ^
  1238 | 
  1239 |         await browser
  1240 |           .elementByCss('#to-head-1')

  at Object.<anonymous> (integration/client-navigation/test/index.test.js:1237:54)
      at runMicrotasks (<anonymous>)

@ijjk
Copy link
Member

ijjk commented Sep 8, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 12.6s 12.3s -292ms
nodeModulesSize 56.7 MB 56.7 MB -103 B
Page Load Tests Overall increase ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
/ failed reqs 0 0
/ total time (seconds) 2.198 2.317 ⚠️ +0.12
/ avg req/sec 1137.59 1079.19 ⚠️ -58.4
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 1.482 1.366 -0.12
/error-in-render avg req/sec 1687.06 1829.84 +142.78
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..a2cc.js gzip 10.8 kB 10.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-4d826ad..023b.js gzip 7.08 kB 7 kB -82 B
webpack-e067..f178.js gzip 751 B 751 B
Overall change 57.6 kB 57.5 kB -82 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.62 kB 6.62 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-4dbc74f..dule.js gzip 6.14 kB 6.07 kB -77 B
webpack-07c5..dule.js gzip 751 B 751 B
Overall change 52.5 kB 52.4 kB -77 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Rendered Page Sizes Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
index.html gzip 970 B 1 kB ⚠️ +33 B
link.html gzip 977 B 1.01 kB ⚠️ +33 B
withRouter.html gzip 963 B 996 B ⚠️ +33 B
Overall change 2.91 kB 3.01 kB ⚠️ +99 B

Diffs

Diff for main-3510944..db9e3ffb3.js
@@ -42,8 +42,13 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
     /***/ DqTX: /***/ function(module, exports, __webpack_require__) {
       "use strict";
 
+      var _slicedToArray = __webpack_require__("J4zp");
+
       exports.__esModule = true;
       exports["default"] = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -78,54 +83,68 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(function(tag) {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var children = tag.props.children;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
+          var elementIter = elements.values();
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          while (true) {
+            // Note: We don't use for-of here to avoid needing to polyfill it.
+            var _elementIter$next = elementIter.next(),
+              done = _elementIter$next.done,
+              value = _elementIter$next.value;
 
-        var newTags = components
-          .map(reactElementToDOM)
-          .filter(function(newTag) {
-            for (var k = 0, len = oldTags.length; k < len; k++) {
-              var oldTag = oldTags[k];
+            if (value == null ? void 0 : value.isEqualNode(newTag)) {
+              oldTags["delete"](value);
+              return;
+            }
 
-              if (oldTag.isEqualNode(newTag)) {
-                oldTags.splice(k, 1);
-                return false;
-              }
+            if (done) {
+              break;
             }
+          }
 
-            return true;
-          });
-        oldTags.forEach(function(t) {
-          return t.parentNode.removeChild(t);
+          elements.add(newTag);
+          headEl.appendChild(newTag);
         });
-        newTags.forEach(function(t) {
-          return headEl.insertBefore(t, headCountEl);
+        oldTags.forEach(function(oldTag) {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements["delete"](oldTag);
         });
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(function(_ref2) {
+            var _ref3 = _slicedToArray(_ref2, 2),
+              type = _ref3[0],
+              props = _ref3[1];
+
+            return /*#__PURE__*/ (0, _react.createElement)(type, props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -133,27 +152,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
             var promise = (updatePromise = Promise.resolve().then(function() {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(function(h) {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var children = titleComponent.props.children;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(function(
-                type
-              ) {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -304,7 +303,8 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
         assetPrefix = data.assetPrefix,
         runtimeConfig = data.runtimeConfig,
         dynamicIds = data.dynamicIds,
-        isFallback = data.isFallback;
+        isFallback = data.isFallback,
+        initialHeadData = data.head;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
 
@@ -343,7 +343,7 @@ _N_E = (window["webpackJsonp_N_E"] = window["webpackJsonp_N_E"] || []).push([
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager["default"])();
+      var headManager = (0, _headManager["default"])(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
Diff for main-50ad093..33.module.js
@@ -19,6 +19,9 @@
 
       exports.__esModule = true;
       exports.default = initHeadManager;
+
+      var _react = __webpack_require__("q1tI");
+
       var DOMAttributeNames = {
         acceptCharset: "accept-charset",
         className: "class",
@@ -51,48 +54,63 @@
         return el;
       }
 
-      function updateElements(type, components) {
+      function updateElements(elements, components, removeOldTags) {
         var headEl = document.getElementsByTagName("head")[0];
-        var headCountEl = headEl.querySelector("meta[name=next-head-count]");
+        var oldTags = new Set(elements);
+        components.forEach(tag => {
+          if (tag.type === "title") {
+            var title = "";
+
+            if (tag) {
+              var { children } = tag.props;
+              title =
+                typeof children === "string" ? children : children.join("");
+            }
 
-        if (false) {
-        }
+            if (title !== document.title) document.title = title;
+            return;
+          }
 
-        var headCount = Number(headCountEl.content);
-        var oldTags = [];
+          var newTag = reactElementToDOM(tag);
+          var elementIter = elements.values();
 
-        for (
-          var i = 0, j = headCountEl.previousElementSibling;
-          i < headCount;
-          i++, j = j.previousElementSibling
-        ) {
-          if (j.tagName.toLowerCase() === type) {
-            oldTags.push(j);
-          }
-        }
+          while (true) {
+            // Note: We don't use for-of here to avoid needing to polyfill it.
+            var { done, value } = elementIter.next();
 
-        var newTags = components.map(reactElementToDOM).filter(newTag => {
-          for (var k = 0, len = oldTags.length; k < len; k++) {
-            var oldTag = oldTags[k];
+            if (value == null ? void 0 : value.isEqualNode(newTag)) {
+              oldTags.delete(value);
+              return;
+            }
 
-            if (oldTag.isEqualNode(newTag)) {
-              oldTags.splice(k, 1);
-              return false;
+            if (done) {
+              break;
             }
           }
 
-          return true;
+          elements.add(newTag);
+          headEl.appendChild(newTag);
+        });
+        oldTags.forEach(oldTag => {
+          if (removeOldTags) {
+            oldTag.parentNode.removeChild(oldTag);
+          }
+
+          elements.delete(oldTag);
         });
-        oldTags.forEach(t => t.parentNode.removeChild(t));
-        newTags.forEach(t => headEl.insertBefore(t, headCountEl));
-        headCountEl.content = (
-          headCount -
-          oldTags.length +
-          newTags.length
-        ).toString();
       }
 
-      function initHeadManager() {
+      function initHeadManager(initialHeadEntries) {
+        var headEl = document.getElementsByTagName("head")[0];
+        var elements = new Set(headEl.children);
+        updateElements(
+          elements,
+          initialHeadEntries.map(_ref2 => {
+            var [type, props] = _ref2;
+            return /*#__PURE__*/ (0, _react.createElement)(type, props);
+          }),
+          false
+        );
         var updatePromise = null;
         return {
           mountedInstances: new Set(),
@@ -100,25 +118,7 @@
             var promise = (updatePromise = Promise.resolve().then(() => {
               if (promise !== updatePromise) return;
               updatePromise = null;
-              var tags = {};
-              head.forEach(h => {
-                var components = tags[h.type] || [];
-                components.push(h);
-                tags[h.type] = components;
-              });
-              var titleComponent = tags.title ? tags.title[0] : null;
-              var title = "";
-
-              if (titleComponent) {
-                var { children } = titleComponent.props;
-                title =
-                  typeof children === "string" ? children : children.join("");
-              }
-
-              if (title !== document.title) document.title = title;
-              ["meta", "base", "link", "style", "script"].forEach(type => {
-                updateElements(type, tags[type] || []);
-              });
+              updateElements(elements, head, true);
             }));
           }
         };
@@ -221,7 +221,8 @@
         assetPrefix,
         runtimeConfig,
         dynamicIds,
-        isFallback
+        isFallback,
+        head: initialHeadData
       } = data;
       var prefix = assetPrefix || ""; // With dynamic assetPrefix it's no longer possible to set assetPrefix at the build time
       // So, this is how we do it in the client side at runtime
@@ -254,7 +255,7 @@
 
       window.__NEXT_P = [];
       window.__NEXT_P.push = register;
-      var headManager = (0, _headManager.default)();
+      var headManager = (0, _headManager.default)(initialHeadData);
       var appElement = document.getElementById("__next");
       var lastAppProps;
       var lastRenderReject;
Diff for index.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-bf808561d88dbb3deb8e.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +85,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-f47b878d28b6d5c0b83e.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-bf808561d88dbb3deb8e.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-bf808561d88dbb3deb8e.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -56,7 +55,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -87,13 +90,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-f47b878d28b6d5c0b83e.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-bf808561d88dbb3deb8e.module.js"
       async=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -3,11 +3,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width" />
-    <meta name="next-head-count" content="2" />
     <noscript data-n-css="true"></noscript>
     <link
       rel="preload"
-      href="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      href="/_next/static/chunks/main-bf808561d88dbb3deb8e.module.js"
       as="script"
       crossorigin="anonymous"
     />
@@ -51,7 +50,11 @@
         "query": {},
         "buildId": "BUILD_ID",
         "isFallback": false,
-        "gip": true
+        "gip": true,
+        "head": [
+          ["meta", { "charSet": "utf-8" }],
+          ["meta", { "name": "viewport", "content": "width=device-width" }]
+        ]
       }
     </script>
     <script crossorigin="anonymous" nomodule="">
@@ -82,13 +85,13 @@
       src="/_next/static/chunks/polyfills-f73ba3fc145972ef83e9.js"
     ></script>
     <script
-      src="/_next/static/chunks/main-3510944e905db9e3ffb3.js"
+      src="/_next/static/chunks/main-f47b878d28b6d5c0b83e.js"
       async=""
       crossorigin="anonymous"
       nomodule=""
     ></script>
     <script
-      src="/_next/static/chunks/main-50ad093b2ad8b3039f33.module.js"
+      src="/_next/static/chunks/main-bf808561d88dbb3deb8e.module.js"
       async=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
buildDuration 14.1s 14.2s ⚠️ +95ms
nodeModulesSize 56.7 MB 56.7 MB -103 B
Client Bundles (main, webpack, commons) Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..a2cc.js gzip 10.8 kB 10.8 kB
framework.HASH.js gzip 39 kB 39 kB
main-4d826ad..023b.js gzip 7.08 kB N/A N/A
webpack-e067..f178.js gzip 751 B 751 B
main-b5a38c9..8030.js gzip N/A 7 kB N/A
Overall change 57.6 kB 57.5 kB -82 B
Client Bundles (main, webpack, commons) Modern Overall decrease ✓
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
677f882d2ed8..dule.js gzip 6.62 kB 6.62 kB
framework.HA..dule.js gzip 39 kB 39 kB
main-4dbc74f..dule.js gzip 6.14 kB N/A N/A
webpack-07c5..dule.js gzip 751 B 751 B
main-f90df29..dule.js gzip N/A 6.07 kB N/A
Overall change 52.5 kB 52.4 kB -77 B
Legacy Client Bundles (polyfills)
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
polyfills-4b..e242.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-9a0b9e1..b37e.js gzip 1.28 kB 1.28 kB
_error-ed1b0..8fbd.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-d2344ce..8b36.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.71 kB 7.71 kB
Client Pages Modern
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_app-75d3a82..dule.js gzip 625 B 625 B
_error-4469a..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-f8c0daf..dule.js gzip 1.26 kB 1.26 kB
routerDirect..dule.js gzip 284 B 284 B
withRouter-f..dule.js gzip 282 B 282 B
Overall change 5.36 kB 5.36 kB
Client Build Manifests
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_buildManifest.js gzip 322 B 322 B
_buildManife..dule.js gzip 329 B 329 B
Overall change 651 B 651 B
Serverless bundles Overall increase ⚠️
vercel/next.js canary azukaru/next.js x-fix-next-head-compat Change
_error.js 1.03 MB 1.03 MB ⚠️ +174 B
404.html 4.22 kB 4.34 kB ⚠️ +115 B
hooks.html 3.86 kB 3.92 kB ⚠️ +56 B
index.js 1.03 MB 1.03 MB ⚠️ +174 B
link.js 1.08 MB 1.08 MB ⚠️ +174 B
routerDirect.js 1.07 MB 1.07 MB ⚠️ +174 B
withRouter.js 1.07 MB 1.07 MB ⚠️ +174 B
Overall change 5.3 MB 5.31 MB ⚠️ +1.04 kB
Commit: 860e2fb

@devknoll devknoll requested a review from Timer September 8, 2020 21:49
@kodiakhq kodiakhq bot merged commit 039eb81 into vercel:canary Sep 9, 2020
HitoriSensei pushed a commit to HitoriSensei/next.js that referenced this pull request Sep 26, 2020
Removes `next-head-count`, improving support for 3rd party libraries that insert or append new elements to `<head>`.

---

This is more or less what a solution with a `data-` attribute would look like, except that instead of directly searching for elements with that attribute, we serialize the elements expected in `<head>` and then find them/assume ownership of them during initialization (in a manner similar to React's reconciliation) based on their properties.

There are two main assumptions here:
1. Content is served with compression, so duplicate serialization of e.g. inline script or style tags doesn't have a meaningful impact. Storing a hash would be a potential optimization.
2. 3rd party libraries primarily only insert new, unique elements to head. Libraries trying to actively manage elements that overlap with those that Next.js claims ownership of will still be unsupported.

The reason for this roundabout approach is that I'd really like to avoid `data-` if possible, for maximum compatibility. Implicitly adding an attribute could be a breaking change for some class of tools or crawlers and makes it otherwise impossible to insert raw HTML into `<head>`. Adding an unexpected attribute is why the original `class="next-head"` approach was problematic in the first place!

That said, while I don't expect this to be more problematic than `next-head-count` (anything that would break in this new model also should have broken in the old model), if that does end up being the case, it might make sense to just bite the bullet.

Fixes vercel#11012
Closes vercel#16707

---

cc @Timer @timneutkens
devknoll added a commit to azukaru/next.js that referenced this pull request Nov 2, 2020
devknoll added a commit to azukaru/next.js that referenced this pull request Nov 2, 2020
devknoll added a commit to azukaru/next.js that referenced this pull request Nov 2, 2020
Timer added a commit that referenced this pull request Nov 10, 2020
Co-authored-by: Joe Haddad <joe.haddad@zeit.co>
@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

next/head removing injected scripts
4 participants