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

Adds first input delay performance metric #8884

Merged
merged 12 commits into from
Apr 13, 2020

Conversation

housseindjirdeh
Copy link
Collaborator

PR uses a modified version of the First Input Delay polyfill that measures the first input (and delay) before hydration as well as after.

Note: #8480 needs to be merged in before this. I'll then update this PR to relay FID to user code.

@timneutkens
Copy link
Member

@housseindjirdeh could you fix the merge conflicts?

@Timer had the concern that this PR increase bundles sizes.

@Timer Timer added this to the 9.1.3 milestone Oct 27, 2019
@Timer Timer modified the milestones: 9.1.3, 9.1.4 Nov 8, 2019
@Timer Timer modified the milestones: 9.1.4, 9.1.5 Nov 19, 2019
@housseindjirdeh housseindjirdeh force-pushed the first-input-delay branch 2 times, most recently from 3992020 to d1a54bd Compare November 21, 2019 16:55
@ijjk
Copy link
Member

ijjk commented Nov 21, 2019

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 12s 12.4s ⚠️ +407ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.16 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +415 B
main-HASH.js gzip 6.33 kB 6.48 kB ⚠️ +147 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +415 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +404 B
main-HASH.module.js gzip 5.3 kB 5.44 kB ⚠️ +144 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +404 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html 4.06 kB 6.75 kB ⚠️ +2.69 kB
index.html gzip 1.04 kB 1.84 kB ⚠️ +798 B
link.html 4.58 kB 7.28 kB ⚠️ +2.69 kB
link.html gzip 1.12 kB 1.92 kB ⚠️ +801 B
withRouter.html 4.59 kB 7.28 kB ⚠️ +2.69 kB
withRouter.html gzip 1.1 kB 1.91 kB ⚠️ +801 B
Overall change 13.2 kB 21.3 kB ⚠️ +8.09 kB

Diffs

Diff for main-HASH.js
@@ -850,6 +850,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -889,11 +890,34 @@ function clearMarks() {
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(function (mark) {
     return performance.clearMarks(mark);
   });
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(function (measure) {
     return performance.clearMeasures(measure);
   });
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay(function (delay, event) {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var _hydrationMeasures$ = hydrationMeasures[0],
+          startTime = _hydrationMeasures$.startTime,
+          duration = _hydrationMeasures$.duration;
+      var hydrateEnd = startTime + duration; // TODO: Instead of console.logs, expose values to user code through perf relayer (next.js/pull/8480)
+
+      console.log('First input after hydration', "start: " + event.timeStamp, "delay: " + delay);
+      console.log('Delta between hydration end and first input', "diff: " + (event.timeStamp - hydrateEnd));
+    } else {
+      console.log('First input before hydration', "start: " + event.timeStamp, "delay: " + delay);
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var children = _ref5.children;
   return _react["default"].createElement(Container, {
Diff for main-HASH.module.js
@@ -627,6 +627,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -664,9 +665,33 @@ function markRenderComplete() {
 function clearMarks() {
   ;
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(mark => performance.clearMarks(mark));
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(measure => performance.clearMeasures(measure));
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay((delay, event) => {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var {
+        startTime,
+        duration
+      } = hydrationMeasures[0];
+      var hydrateEnd = startTime + duration; // TODO: Instead of console.logs, expose values to user code through perf relayer (next.js/pull/8480)
+
+      console.log('First input after hydration', "start: " + event.timeStamp, "delay: " + delay);
+      console.log('Delta between hydration end and first input', "diff: " + (event.timeStamp - hydrateEnd));
+    } else {
+      console.log('First input before hydration', "start: " + event.timeStamp, "delay: " + delay);
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var {
     children
Diff for index.html
@@ -12,7 +12,7 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-4b0140f328329d8c4fe7.module.js"
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/framework.9c9aa574c484a7d0240e.module.js"
         as="script" crossorigin="anonymous" />
@@ -20,6 +20,94 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/de003c3a9d308750aa009870a5926f9b18ab31f4.dda3a797bb804537ed85.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -44,9 +132,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-f0e3b16a229bdd59944b.js"
+        <script src="/_next/static/runtime/main-c75707e0b5c454789372.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <script src="/_next/static/runtime/main-4b0140f328329d8c4fe7.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/chunks/framework.4c64484d8a631a55b435.js"
         async="" crossorigin="anonymous" nomodule=""></script>
Diff for link.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-4b0140f328329d8c4fe7.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -67,9 +155,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-f0e3b16a229bdd59944b.js"
+        <script src="/_next/static/runtime/main-c75707e0b5c454789372.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <script src="/_next/static/runtime/main-4b0140f328329d8c4fe7.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>
Diff for withRouter.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-4b0140f328329d8c4fe7.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -65,9 +153,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-f0e3b16a229bdd59944b.js"
+        <script src="/_next/static/runtime/main-c75707e0b5c454789372.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <script src="/_next/static/runtime/main-4b0140f328329d8c4fe7.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 12.6s 13.1s ⚠️ +558ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.16 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +415 B
main-HASH.js gzip 6.33 kB 6.48 kB ⚠️ +147 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +415 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +404 B
main-HASH.module.js gzip 5.3 kB 5.44 kB ⚠️ +144 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +404 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js 255 kB 259 kB ⚠️ +3.32 kB
_error.js gzip 67.9 kB 68.7 kB ⚠️ +842 B
hooks.html 4.19 kB 6.89 kB ⚠️ +2.69 kB
hooks.html gzip 1.07 kB 1.86 kB ⚠️ +793 B
index.js 256 kB 259 kB ⚠️ +3.32 kB
index.js gzip 68 kB 68.9 kB ⚠️ +847 B
link.js 288 kB 291 kB ⚠️ +3.32 kB
link.js gzip 77.1 kB 77.9 kB ⚠️ +849 B
routerDirect.js 281 kB 284 kB ⚠️ +3.32 kB
routerDirect.js gzip 75.1 kB 75.9 kB ⚠️ +848 B
withRouter.js 281 kB 284 kB ⚠️ +3.32 kB
withRouter.js gzip 75.2 kB 76 kB ⚠️ +843 B
Overall change 1.37 MB 1.38 MB ⚠️ +19.3 kB

Commit: d1a54bd

@ijjk
Copy link
Member

ijjk commented Nov 21, 2019

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 13.4s 13.9s ⚠️ +518ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.04 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html 4.06 kB 6.75 kB ⚠️ +2.69 kB
index.html gzip 1.04 kB 1.83 kB ⚠️ +797 B
link.html 4.58 kB 7.28 kB ⚠️ +2.69 kB
link.html gzip 1.12 kB 1.92 kB ⚠️ +802 B
withRouter.html 4.59 kB 7.28 kB ⚠️ +2.69 kB
withRouter.html gzip 1.1 kB 1.91 kB ⚠️ +801 B
Overall change 13.2 kB 21.3 kB ⚠️ +8.09 kB

Diffs

Diff for main-HASH.js
@@ -850,6 +850,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -889,11 +890,50 @@ function clearMarks() {
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(function (mark) {
     return performance.clearMarks(mark);
   });
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(function (measure) {
     return performance.clearMeasures(measure);
   });
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay(function (delay, event) {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var _hydrationMeasures$ = hydrationMeasures[0],
+          startTime = _hydrationMeasures$.startTime,
+          duration = _hydrationMeasures$.duration;
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var children = _ref5.children;
   return _react["default"].createElement(Container, {
Diff for main-HASH.module.js
@@ -627,6 +627,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -664,9 +665,49 @@ function markRenderComplete() {
 function clearMarks() {
   ;
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(mark => performance.clearMarks(mark));
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(measure => performance.clearMeasures(measure));
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay((delay, event) => {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var {
+        startTime,
+        duration
+      } = hydrationMeasures[0];
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var {
     children
Diff for index.html
@@ -12,7 +12,7 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-b9043a785a9af4746b1e.module.js"
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/framework.9c9aa574c484a7d0240e.module.js"
         as="script" crossorigin="anonymous" />
@@ -20,6 +20,94 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/de003c3a9d308750aa009870a5926f9b18ab31f4.dda3a797bb804537ed85.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -44,9 +132,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-f0e3b16a229bdd59944b.js"
+        <script src="/_next/static/runtime/main-a009e59d09246fa2b410.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <script src="/_next/static/runtime/main-b9043a785a9af4746b1e.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/chunks/framework.4c64484d8a631a55b435.js"
         async="" crossorigin="anonymous" nomodule=""></script>
Diff for link.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-b9043a785a9af4746b1e.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -67,9 +155,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-f0e3b16a229bdd59944b.js"
+        <script src="/_next/static/runtime/main-a009e59d09246fa2b410.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <script src="/_next/static/runtime/main-b9043a785a9af4746b1e.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>
Diff for withRouter.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-b9043a785a9af4746b1e.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -65,9 +153,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-f0e3b16a229bdd59944b.js"
+        <script src="/_next/static/runtime/main-a009e59d09246fa2b410.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-0e1c4b24306318fc1d32.module.js"
+        <script src="/_next/static/runtime/main-b9043a785a9af4746b1e.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 13.7s 14.1s ⚠️ +349ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.04 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js 255 kB 259 kB ⚠️ +3.32 kB
_error.js gzip 67.9 kB 68.7 kB ⚠️ +842 B
hooks.html 4.19 kB 6.89 kB ⚠️ +2.69 kB
hooks.html gzip 1.07 kB 1.86 kB ⚠️ +792 B
index.js 256 kB 259 kB ⚠️ +3.32 kB
index.js gzip 68 kB 68.9 kB ⚠️ +848 B
link.js 288 kB 291 kB ⚠️ +3.32 kB
link.js gzip 77.1 kB 77.9 kB ⚠️ +849 B
routerDirect.js 281 kB 284 kB ⚠️ +3.32 kB
routerDirect.js gzip 75.1 kB 75.9 kB ⚠️ +848 B
withRouter.js 281 kB 284 kB ⚠️ +3.32 kB
withRouter.js gzip 75.2 kB 76 kB ⚠️ +843 B
Overall change 1.37 MB 1.38 MB ⚠️ +19.3 kB

Commit: bd9ac98

@housseindjirdeh housseindjirdeh force-pushed the first-input-delay branch 2 times, most recently from a9fdd5f to a8c985c Compare November 21, 2019 22:20
const { startTime, duration } = hydrationMeasures[0]
const hydrateEnd = startTime + duration

if (onPerfEntry) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ijjk
Copy link
Member

ijjk commented Nov 21, 2019

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 15.5s 15.9s ⚠️ +399ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html 4.06 kB 6.75 kB ⚠️ +2.69 kB
index.html gzip 1.04 kB 1.83 kB ⚠️ +798 B
link.html 4.58 kB 7.28 kB ⚠️ +2.69 kB
link.html gzip 1.12 kB 1.92 kB ⚠️ +798 B
withRouter.html 4.59 kB 7.28 kB ⚠️ +2.69 kB
withRouter.html gzip 1.1 kB 1.9 kB ⚠️ +797 B
Overall change 13.2 kB 21.3 kB ⚠️ +8.09 kB

Diffs

Diff for main-HASH.js
@@ -428,7 +428,7 @@ var _dataManager = __webpack_require__("0IRE");
 var _querystring = __webpack_require__("s4NR");
 
 var _isDynamic = __webpack_require__("/jkW");
-/* global location */
+/* global location, hydrationMetrics */
 
 
 if (!window.Promise) {
@@ -850,6 +850,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -889,11 +890,50 @@ function clearMarks() {
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(function (mark) {
     return performance.clearMarks(mark);
   });
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(function (measure) {
     return performance.clearMeasures(measure);
   });
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay(function (delay, event) {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var _hydrationMeasures$ = hydrationMeasures[0],
+          startTime = _hydrationMeasures$.startTime,
+          duration = _hydrationMeasures$.duration;
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var children = _ref5.children;
   return _react["default"].createElement(Container, {
Diff for main-HASH.module.js
@@ -332,7 +332,7 @@ var _dataManager = __webpack_require__("0IRE");
 var _querystring = __webpack_require__("s4NR");
 
 var _isDynamic = __webpack_require__("/jkW");
-/* global location */
+/* global location, hydrationMetrics */
 
 
 if (!window.Promise) {
@@ -627,6 +627,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -664,9 +665,49 @@ function markRenderComplete() {
 function clearMarks() {
   ;
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(mark => performance.clearMarks(mark));
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(measure => performance.clearMeasures(measure));
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay((delay, event) => {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var {
+        startTime,
+        duration
+      } = hydrationMeasures[0];
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var {
     children
Diff for index.html
@@ -12,7 +12,7 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/framework.9c9aa574c484a7d0240e.module.js"
         as="script" crossorigin="anonymous" />
@@ -20,6 +20,94 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/de003c3a9d308750aa009870a5926f9b18ab31f4.dda3a797bb804537ed85.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -44,9 +132,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/chunks/framework.4c64484d8a631a55b435.js"
         async="" crossorigin="anonymous" nomodule=""></script>
Diff for link.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -67,9 +155,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>
Diff for withRouter.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -65,9 +153,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 15.7s 16.1s ⚠️ +407ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js 255 kB 259 kB ⚠️ +3.32 kB
_error.js gzip 67.9 kB 68.7 kB ⚠️ +844 B
hooks.html 4.19 kB 6.89 kB ⚠️ +2.69 kB
hooks.html gzip 1.07 kB 1.86 kB ⚠️ +793 B
index.js 256 kB 259 kB ⚠️ +3.32 kB
index.js gzip 68 kB 68.9 kB ⚠️ +851 B
link.js 288 kB 291 kB ⚠️ +3.32 kB
link.js gzip 77.1 kB 77.9 kB ⚠️ +851 B
routerDirect.js 281 kB 284 kB ⚠️ +3.32 kB
routerDirect.js gzip 75.1 kB 75.9 kB ⚠️ +849 B
withRouter.js 281 kB 284 kB ⚠️ +3.32 kB
withRouter.js gzip 75.2 kB 76 kB ⚠️ +845 B
Overall change 1.37 MB 1.38 MB ⚠️ +19.3 kB

Commit: a8c985c

@ijjk
Copy link
Member

ijjk commented Nov 21, 2019

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 15.2s 15.2s ⚠️ +71ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html 4.06 kB 6.75 kB ⚠️ +2.69 kB
index.html gzip 1.04 kB 1.83 kB ⚠️ +798 B
link.html 4.58 kB 7.28 kB ⚠️ +2.69 kB
link.html gzip 1.12 kB 1.92 kB ⚠️ +798 B
withRouter.html 4.59 kB 7.28 kB ⚠️ +2.69 kB
withRouter.html gzip 1.1 kB 1.9 kB ⚠️ +797 B
Overall change 13.2 kB 21.3 kB ⚠️ +8.09 kB

Diffs

Diff for main-HASH.js
@@ -428,7 +428,7 @@ var _dataManager = __webpack_require__("0IRE");
 var _querystring = __webpack_require__("s4NR");
 
 var _isDynamic = __webpack_require__("/jkW");
-/* global location */
+/* global location, hydrationMetrics */
 
 
 if (!window.Promise) {
@@ -850,6 +850,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -889,11 +890,50 @@ function clearMarks() {
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(function (mark) {
     return performance.clearMarks(mark);
   });
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(function (measure) {
     return performance.clearMeasures(measure);
   });
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay(function (delay, event) {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var _hydrationMeasures$ = hydrationMeasures[0],
+          startTime = _hydrationMeasures$.startTime,
+          duration = _hydrationMeasures$.duration;
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var children = _ref5.children;
   return _react["default"].createElement(Container, {
Diff for main-HASH.module.js
@@ -332,7 +332,7 @@ var _dataManager = __webpack_require__("0IRE");
 var _querystring = __webpack_require__("s4NR");
 
 var _isDynamic = __webpack_require__("/jkW");
-/* global location */
+/* global location, hydrationMetrics */
 
 
 if (!window.Promise) {
@@ -627,6 +627,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -664,9 +665,49 @@ function markRenderComplete() {
 function clearMarks() {
   ;
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(mark => performance.clearMarks(mark));
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(measure => performance.clearMeasures(measure));
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay((delay, event) => {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var {
+        startTime,
+        duration
+      } = hydrationMeasures[0];
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var {
     children
Diff for index.html
@@ -12,7 +12,7 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/framework.9c9aa574c484a7d0240e.module.js"
         as="script" crossorigin="anonymous" />
@@ -20,6 +20,94 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/de003c3a9d308750aa009870a5926f9b18ab31f4.dda3a797bb804537ed85.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -44,9 +132,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/chunks/framework.4c64484d8a631a55b435.js"
         async="" crossorigin="anonymous" nomodule=""></script>
Diff for link.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -67,9 +155,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>
Diff for withRouter.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type == 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -65,9 +153,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 15.9s 15.8s -81ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js 255 kB 259 kB ⚠️ +3.32 kB
_error.js gzip 67.9 kB 68.7 kB ⚠️ +844 B
hooks.html 4.19 kB 6.89 kB ⚠️ +2.69 kB
hooks.html gzip 1.07 kB 1.86 kB ⚠️ +793 B
index.js 256 kB 259 kB ⚠️ +3.32 kB
index.js gzip 68 kB 68.9 kB ⚠️ +851 B
link.js 288 kB 291 kB ⚠️ +3.32 kB
link.js gzip 77.1 kB 77.9 kB ⚠️ +851 B
routerDirect.js 281 kB 284 kB ⚠️ +3.32 kB
routerDirect.js gzip 75.1 kB 75.9 kB ⚠️ +849 B
withRouter.js 281 kB 284 kB ⚠️ +3.32 kB
withRouter.js gzip 75.2 kB 76 kB ⚠️ +845 B
Overall change 1.37 MB 1.38 MB ⚠️ +19.3 kB

Commit: a8c985c

@ijjk
Copy link
Member

ijjk commented Nov 21, 2019

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 13.9s 14.2s ⚠️ +236ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html 4.06 kB 6.75 kB ⚠️ +2.7 kB
index.html gzip 1.04 kB 1.83 kB ⚠️ +798 B
link.html 4.58 kB 7.28 kB ⚠️ +2.7 kB
link.html gzip 1.12 kB 1.92 kB ⚠️ +799 B
withRouter.html 4.59 kB 7.29 kB ⚠️ +2.7 kB
withRouter.html gzip 1.1 kB 1.91 kB ⚠️ +800 B
Overall change 13.2 kB 21.3 kB ⚠️ +8.09 kB

Diffs

Diff for main-HASH.js
@@ -428,7 +428,7 @@ var _dataManager = __webpack_require__("0IRE");
 var _querystring = __webpack_require__("s4NR");
 
 var _isDynamic = __webpack_require__("/jkW");
-/* global location */
+/* global location, hydrationMetrics */
 
 
 if (!window.Promise) {
@@ -850,6 +850,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -889,11 +890,50 @@ function clearMarks() {
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(function (mark) {
     return performance.clearMarks(mark);
   });
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(function (measure) {
     return performance.clearMeasures(measure);
   });
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay(function (delay, event) {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var _hydrationMeasures$ = hydrationMeasures[0],
+          startTime = _hydrationMeasures$.startTime,
+          duration = _hydrationMeasures$.duration;
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var children = _ref5.children;
   return _react["default"].createElement(Container, {
Diff for main-HASH.module.js
@@ -332,7 +332,7 @@ var _dataManager = __webpack_require__("0IRE");
 var _querystring = __webpack_require__("s4NR");
 
 var _isDynamic = __webpack_require__("/jkW");
-/* global location */
+/* global location, hydrationMetrics */
 
 
 if (!window.Promise) {
@@ -627,6 +627,7 @@ function renderReactElement(reactEl, domEl) {
 
 function markHydrateComplete() {
   if (!_utils.SUPPORTS_PERFORMANCE_USER_TIMING) return;
+  measureFid();
   performance.mark('afterHydrate'); // mark end of hydration
 
   performance.measure('Next.js-before-hydration', 'navigationStart', 'beforeRender');
@@ -664,9 +665,49 @@ function markRenderComplete() {
 function clearMarks() {
   ;
   ['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(mark => performance.clearMarks(mark));
+}
+
+function clearMeasures() {
+  ;
   ['Next.js-before-hydration', 'Next.js-hydration', 'Next.js-route-change-to-render', 'Next.js-render'].forEach(measure => performance.clearMeasures(measure));
 }
 
+function measureFid() {
+  hydrationMetrics.onInputDelay((delay, event) => {
+    var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+
+    if (hydrationMeasures.length > 0) {
+      var {
+        startTime,
+        duration
+      } = hydrationMeasures[0];
+      var hydrateEnd = startTime + duration;
+
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-after-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+        onPerfEntry({
+          name: 'time-to-first-input-after-hydration',
+          startTime: hydrateEnd,
+          value: event.timeStamp - hydrateEnd
+        });
+      }
+    } else {
+      if (onPerfEntry) {
+        onPerfEntry({
+          name: 'first-input-delay-before-hydration',
+          startTime: event.timeStamp,
+          value: delay
+        });
+      }
+    }
+  });
+  clearMeasures();
+}
+
 function AppContainer(_ref5) {
   var {
     children
Diff for index.html
@@ -12,7 +12,7 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/framework.9c9aa574c484a7d0240e.module.js"
         as="script" crossorigin="anonymous" />
@@ -20,6 +20,94 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/de003c3a9d308750aa009870a5926f9b18ab31f4.dda3a797bb804537ed85.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type === 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -44,9 +132,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/runtime/webpack-7928590be3ef2e55b835.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/chunks/framework.4c64484d8a631a55b435.js"
         async="" crossorigin="anonymous" nomodule=""></script>
Diff for link.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type === 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -67,9 +155,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>
Diff for withRouter.html
@@ -20,8 +20,96 @@
         as="script" crossorigin="anonymous" />
         <link rel="preload" href="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         as="script" crossorigin="anonymous" />
-        <link rel="preload" href="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <link rel="preload" href="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         as="script" crossorigin="anonymous" />
+        <script>
+            (function fidPolyfill(addEventListener, removeEventListener) {
+              var firstInputEvent;
+              var firstInputDelay;
+              var firstInputTimeStamp;
+              var callbacks = [];
+              var listenerOpts = {
+                passive: true,
+                capture: true
+              };
+              var startTimeStamp = +new Date();
+              var pointerup = 'pointerup';
+              var pointercancel = 'pointercancel';
+            
+              function onInputDelay(callback) {
+                callbacks.push(callback);
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function recordInputDelay(delay, evt) {
+                firstInputEvent = evt;
+                firstInputDelay = delay;
+                firstInputTimeStamp = +new Date();
+                reportInputDelayIfRecordedAndValid();
+              }
+            
+              function reportInputDelayIfRecordedAndValid() {
+                var hydrationMeasures = performance.getEntriesByName('Next.js-hydration', 'measure');
+                var firstInputStart = firstInputTimeStamp - startTimeStamp;
+            
+                if (firstInputDelay >= 0 && firstInputDelay < firstInputStart && (hydrationMeasures.length === 0 || hydrationMeasures[0].startTime < firstInputStart)) {
+                  callbacks.forEach(function (callback) {
+                    callback(firstInputDelay, firstInputEvent);
+                  }); // If the app is already hydrated, that means the first "post-hydration" input
+                  // has been measured and listeners can be removed
+            
+                  if (hydrationMeasures.length > 0) {
+                    eachEventType(removeEventListener);
+                    callbacks = [];
+                  }
+                }
+              }
+            
+              function onPointerDown(delay, evt) {
+                function onPointerUp() {
+                  recordInputDelay(delay, evt);
+                }
+            
+                function onPointerCancel() {
+                  removePointerEventListeners();
+                }
+            
+                function removePointerEventListeners() {
+                  removeEventListener(pointerup, onPointerUp, listenerOpts);
+                  removeEventListener(pointercancel, onPointerCancel, listenerOpts);
+                }
+            
+                addEventListener(pointerup, onPointerUp, listenerOpts);
+                addEventListener(pointercancel, onPointerCancel, listenerOpts);
+              }
+            
+              function onInput(evt) {
+                if (evt.cancelable) {
+                  var isEpochTime = evt.timeStamp > 1e12;
+                  var now = isEpochTime ? +new Date() : performance.now();
+                  var delay = now - evt.timeStamp;
+            
+                  if (evt.type === 'pointerdown') {
+                    onPointerDown(delay, evt);
+                  } else {
+                    recordInputDelay(delay, evt);
+                  }
+                }
+              }
+            
+              function eachEventType(callback) {
+                var eventTypes = ['click', 'mousedown', 'keydown', 'touchstart', 'pointerdown'];
+                eventTypes.forEach(function (eventType) {
+                  callback(eventType, onInput, listenerOpts);
+                });
+              }
+            
+              eachEventType(addEventListener);
+              var context = self;
+              context['hydrationMetrics'] = context['hydrationMetrics'] || {};
+              context['hydrationMetrics']['onInputDelay'] = onInputDelay;
+            })(addEventListener, removeEventListener)
+        </script>
     </head>
     
     <body>
@@ -65,9 +153,9 @@
         async="" crossorigin="anonymous" nomodule=""></script>
         <script src="/_next/static/chunks/16b1a72255206b7853bf6603dc58ec83de39b142.5f63c642fc68b85bc4ba.module.js"
         async="" crossorigin="anonymous" type="module"></script>
-        <script src="/_next/static/runtime/main-cfa30afa3978aa8061c5.js"
+        <script src="/_next/static/runtime/main-e008657343e015483bcd.js"
         async="" crossorigin="anonymous" nomodule=""></script>
-        <script src="/_next/static/runtime/main-2360a070726bccc86ce4.module.js"
+        <script src="/_next/static/runtime/main-62a58a0edbf83429b9b4.module.js"
         async="" crossorigin="anonymous" type="module"></script>
         <script src="/_next/static/BUILD_ID/_buildManifest.js" async=""
         crossorigin="anonymous" nomodule=""></script>

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 14.6s 14.5s -133ms
nodeModulesSize 48.3 MB 48.3 MB ⚠️ +9.2 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js 17.9 kB 18.3 kB ⚠️ +417 B
main-HASH.js gzip 6.33 kB 6.46 kB ⚠️ +127 B
webpack-HASH.js 1.53 kB 1.53 kB
webpack-HASH.js gzip 746 B 746 B
16b1a7225520..ebc17c08d.js 19.8 kB 19.8 kB
16b1a7225520..c08d.js gzip 7.35 kB 7.35 kB
4952ddcd88e7..49d7903c2.js 9.6 kB 9.6 kB
4952ddcd88e7..03c2.js gzip 3.76 kB 3.76 kB
commons.HASH.js 10.4 kB 10.4 kB
commons.HASH.js gzip 3.91 kB 3.91 kB
de003c3a9d30..a824c82eb.js 28.3 kB 28.3 kB
de003c3a9d30..82eb.js gzip 10.7 kB 10.7 kB
framework.HASH.js 125 kB 125 kB
framework.HASH.js gzip 39.4 kB 39.4 kB
Overall change 213 kB 213 kB ⚠️ +417 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js 14.1 kB 14.5 kB ⚠️ +406 B
main-HASH.module.js gzip 5.3 kB 5.42 kB ⚠️ +124 B
webpack-HASH.module.js 1.53 kB 1.53 kB
webpack-HASH..dule.js gzip 746 B 746 B
16b1a7225520..9a.module.js 21.3 kB 21.3 kB
16b1a7225520..dule.js gzip 8.14 kB 8.14 kB
4952ddcd88e7..cc.module.js 11.9 kB 11.9 kB
4952ddcd88e7..dule.js gzip 4.43 kB 4.43 kB
de003c3a9d30..36.module.js 22.2 kB 22.2 kB
de003c3a9d30..dule.js gzip 8.71 kB 8.71 kB
framework.HASH.module.js 125 kB 125 kB
framework.HA..dule.js gzip 39.4 kB 39.4 kB
Overall change 196 kB 197 kB ⚠️ +406 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js 9.11 kB 9.11 kB
polyfills-HASH.js gzip 3.11 kB 3.11 kB
Overall change 9.11 kB 9.11 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js 2.92 kB 2.92 kB
_app.js gzip 1.32 kB 1.32 kB
_error.js 13.2 kB 13.2 kB
_error.js gzip 5.1 kB 5.1 kB
hooks.js 1.92 kB 1.92 kB
hooks.js gzip 941 B 941 B
index.js 318 B 318 B
index.js gzip 222 B 222 B
link.js 6.76 kB 6.76 kB
link.js gzip 2.88 kB 2.88 kB
routerDirect.js 413 B 413 B
routerDirect.js gzip 285 B 285 B
withRouter.js 423 B 423 B
withRouter.js gzip 284 B 284 B
Overall change 26 kB 26 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js 1.54 kB 1.54 kB
_app.module.js gzip 759 B 759 B
_error.module.js 10.5 kB 10.5 kB
_error.module.js gzip 4.26 kB 4.26 kB
hooks.module.js 677 B 677 B
hooks.module.js gzip 384 B 384 B
index.module.js 292 B 292 B
index.module.js gzip 223 B 223 B
link.module.js 5.49 kB 5.49 kB
link.module.js gzip 2.47 kB 2.47 kB
routerDirect.module.js 399 B 399 B
routerDirect..dule.js gzip 285 B 285 B
withRouter.module.js 409 B 409 B
withRouter.m..dule.js gzip 282 B 282 B
Overall change 19.3 kB 19.3 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js 244 B 244 B
_buildManifest.js gzip 201 B 201 B
_buildManifest.module.js 251 B 251 B
_buildManife..dule.js gzip 208 B 208 B
Overall change 495 B 495 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js 255 kB 259 kB ⚠️ +3.32 kB
_error.js gzip 67.9 kB 68.7 kB ⚠️ +845 B
hooks.html 4.19 kB 6.89 kB ⚠️ +2.7 kB
hooks.html gzip 1.07 kB 1.86 kB ⚠️ +793 B
index.js 256 kB 259 kB ⚠️ +3.32 kB
index.js gzip 68 kB 68.9 kB ⚠️ +852 B
link.js 288 kB 291 kB ⚠️ +3.32 kB
link.js gzip 77.1 kB 77.9 kB ⚠️ +852 B
routerDirect.js 281 kB 284 kB ⚠️ +3.32 kB
routerDirect.js gzip 75.1 kB 75.9 kB ⚠️ +850 B
withRouter.js 281 kB 284 kB ⚠️ +3.32 kB
withRouter.js gzip 75.2 kB 76 kB ⚠️ +845 B
Overall change 1.37 MB 1.38 MB ⚠️ +19.3 kB

Commit: 0744279

@housseindjirdeh
Copy link
Collaborator Author

@timneutkens resolved

@Timer I noticed :(. Surprised that primarily just a single method (measureFid) made that much of a jump to be honest. Any thoughts/suggestions?

@Timer Timer modified the milestones: 9.1.5, 9.1.6 Dec 10, 2019
@ijjk ijjk modified the milestones: 9.1.6, 9.2.0 Dec 17, 2019
@@ -518,6 +519,11 @@ export class Head extends Component<
</>
)}
{React.createElement(React.Fragment, {}, ...(headTags || []))}
<script
Copy link
Member

@Timer Timer Dec 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the very first <script> after <link rel=preload> but before the first app <script defer/async>.

h/t @developit

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a helper render method to make sure it's before the only async app script (in AMP mode) 👍

packages/next/client/index.js Outdated Show resolved Hide resolved
@Timer Timer removed this from the 9.2.0 milestone Jan 3, 2020
@ijjk
Copy link
Member

ijjk commented Jan 8, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 15.1s 14.8s -210ms
nodeModulesSize 48.9 MB 48.9 MB ⚠️ +10.8 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js gzip 5.11 kB 5.28 kB ⚠️ +167 B
webpack-HASH.js gzip 746 B 746 B
4952ddcd88e7..54d3.js gzip 4.68 kB 4.68 kB
commons.HASH.js gzip 4.06 kB 4.06 kB
de003c3a9d30..9881.js gzip 13.7 kB 13.7 kB
framework.HASH.js gzip 39.1 kB 39.1 kB
Overall change 67.4 kB 67.6 kB ⚠️ +167 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js gzip 4.17 kB 4.34 kB ⚠️ +174 B
webpack-HASH..dule.js gzip 746 B 746 B
4952ddcd88e7..dule.js gzip 5.56 kB 5.56 kB
de003c3a9d30..dule.js gzip 12.5 kB 12.5 kB
framework.HA..dule.js gzip 39.1 kB 39.1 kB
Overall change 62.1 kB 62.2 kB ⚠️ +174 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js gzip 4.76 kB 4.76 kB
Overall change 4.76 kB 4.76 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js gzip 1.33 kB 1.33 kB
_error.js gzip 4.07 kB 4.07 kB
hooks.js gzip 779 B 779 B
index.js gzip 222 B 222 B
link.js gzip 2.9 kB 2.9 kB
routerDirect.js gzip 283 B 283 B
withRouter.js gzip 282 B 282 B
Overall change 9.87 kB 9.87 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js gzip 757 B 757 B
_error.module.js gzip 3.06 kB 3.06 kB
hooks.module.js gzip 371 B 371 B
index.module.js gzip 212 B 212 B
link.module.js gzip 2.47 kB 2.47 kB
routerDirect..dule.js gzip 273 B 273 B
withRouter.m..dule.js gzip 272 B 272 B
Overall change 7.41 kB 7.41 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js gzip 61 B 61 B
_buildManife..dule.js gzip 61 B 61 B
Overall change 122 B 122 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html gzip 1.02 kB 1.82 kB ⚠️ +799 B
link.html gzip 1.03 kB 1.83 kB ⚠️ +799 B
withRouter.html gzip 1.01 kB 1.81 kB ⚠️ +800 B
Overall change 3.06 kB 5.46 kB ⚠️ +2.4 kB

Diffs

Diff for main-HASH.js
@@ -571,33 +571,28 @@
                         _ref3 = _context.sent
                         app = _ref3.page
                         mod = _ref3.mod
-                        App = app
+                        App = app // if (mod && mod.unstable_onPerformanceData) {
+                        //   onPerfEntry = function({ name, startTime, value, duration }) {
+                        //     mod.unstable_onPerformanceData({ name, startTime, value, duration })
+                        //   }
+                        // }
+
+                        onPerfEntry = false
 
                         if (mod && mod.unstable_onPerformanceData) {
-                          onPerfEntry = function onPerfEntry(_ref3) {
-                            var name = _ref3.name,
-                              startTime = _ref3.startTime,
-                              value = _ref3.value,
-                              duration = _ref3.duration
-                            mod.unstable_onPerformanceData({
-                              name: name,
-                              startTime: startTime,
-                              value: value,
-                              duration: duration,
-                            })
-                          }
+                          onPerfEntry = false
                         }
 
                         initialErr = err
-                        _context.prev = 10
-                        _context.next = 13
+                        _context.prev = 11
+                        _context.next = 14
                         return pageLoader.loadPage(page)
 
-                      case 13:
+                      case 14:
                         Component = _context.sent
 
                         if (true) {
-                          _context.next = 18
+                          _context.next = 19
                           break
                         }
 
@@ -611,7 +606,7 @@
                           (isValidElementType = _require.isValidElementType)
 
                         if (isValidElementType(Component)) {
-                          _context.next = 18
+                          _context.next = 19
                           break
                         }
 
@@ -621,26 +616,26 @@
                             '"'
                         )
 
-                      case 18:
-                        _context.next = 23
+                      case 19:
+                        _context.next = 24
                         break
 
-                      case 20:
-                        _context.prev = 20
-                        _context.t0 = _context['catch'](10)
+                      case 21:
+                        _context.prev = 21
+                        _context.t0 = _context['catch'](11)
                         // This catches errors like throwing in the top level of a module
                         initialErr = _context.t0
 
-                      case 23:
+                      case 24:
                         if (!window.__NEXT_PRELOADREADY) {
-                          _context.next = 26
+                          _context.next = 27
                           break
                         }
 
-                        _context.next = 26
+                        _context.next = 27
                         return window.__NEXT_PRELOADREADY(dynamicIds)
 
-                      case 26:
+                      case 27:
                         exports.router = router = (0, _router.createRouter)(
                           page,
                           query,
@@ -652,10 +647,10 @@
                             Component: Component,
                             wrapApp: wrapApp,
                             err: initialErr,
-                            subscription: function subscription(_ref4, App) {
-                              var Component = _ref4.Component,
-                                props = _ref4.props,
-                                err = _ref4.err
+                            subscription: function subscription(_ref3, App) {
+                              var Component = _ref3.Component,
+                                props = _ref3.props,
+                                err = _ref3.err
                               render({
                                 App: App,
                                 Component: Component,
@@ -678,7 +673,7 @@
                         render(renderCtx)
                         return _context.abrupt('return', emitter)
 
-                      case 31:
+                      case 32:
                       case 'end':
                         return _context.stop()
                     }
@@ -686,7 +681,7 @@
                 },
                 _callee,
                 null,
-                [[10, 20]]
+                [[11, 21]]
               )
             })
           )
@@ -899,8 +894,24 @@
           'beforeRender'
         )
         performance.measure('Next.js-hydration', 'beforeRender', 'afterHydrate')
+        console.log(onPerfEntry, 'eve')
 
         if (onPerfEntry) {
+          console.log('noteven')
+
+          _promise['default']
+            .resolve()
+            .then(function() {
+              return (0,
+              _interopRequireWildcard2['default'])(__webpack_require__('dT2I'))
+            })
+            .then(function(mod) {
+              mod['default'](onPerfEntry)
+            })
+            ['catch'](function(err) {
+              console.error('Error measuring FID', err)
+            })
+
           performance.getEntriesByName('Next.js-hydration').forEach(onPerfEntry)
           performance.getEntriesByName('beforeRender').forEach(onPerfEntry)
         }
@@ -944,18 +955,10 @@
             return performance.clearMarks(mark)
           }
         )
-        ;[
-          'Next.js-before-hydration',
-          'Next.js-hydration',
-          'Next.js-route-change-to-render',
-          'Next.js-render',
-        ].forEach(function(measure) {
-          return performance.clearMeasures(measure)
-        })
       }
 
-      function AppContainer(_ref5) {
-        var children = _ref5.children
+      function AppContainer(_ref4) {
+        var children = _ref4.children
         return _react['default'].createElement(
           Container,
           {
@@ -1006,7 +1009,7 @@
       function _doRender() {
         _doRender = (0, _asyncToGenerator2['default'])(
           /*#__PURE__*/
-          _regeneratorRuntime.mark(function _callee4(_ref6) {
+          _regeneratorRuntime.mark(function _callee4(_ref5) {
             var App,
               Component,
               props,
@@ -1024,10 +1027,10 @@
               while (1) {
                 switch ((_context4.prev = _context4.next)) {
                   case 0:
-                    ;(App = _ref6.App),
-                      (Component = _ref6.Component),
-                      (props = _ref6.props),
-                      (err = _ref6.err) // Usual getInitialProps fetching is handled in next/router
+                    ;(App = _ref5.App),
+                      (Component = _ref5.Component),
+                      (props = _ref5.props),
+                      (err = _ref5.err) // Usual getInitialProps fetching is handled in next/router
                     // this is for when ErrorComponent gets replaced by Component by HMR
 
                     if (
@@ -1174,6 +1177,67 @@
       /***/
     },
 
+    /***/ dT2I: /***/ function(module, exports, __webpack_require__) {
+      'use strict'
+
+      /* global hydrationMetrics */
+
+      var _Object$defineProperty = __webpack_require__('hfKm')
+
+      _Object$defineProperty(exports, '__esModule', {
+        value: true,
+      })
+
+      exports['default'] = function(onPerfEntry) {
+        function clearMeasures() {
+          ;[
+            'Next.js-hydration',
+            'Next.js-before-hydration',
+            'Next.js-route-change-to-render',
+            'Next.js-render',
+          ].forEach(function(measure) {
+            return performance.clearMeasures(measure)
+          })
+        }
+
+        hydrationMetrics.onInputDelay(function(delay, event) {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+
+          if (hydrationMeasures.length > 0) {
+            var _hydrationMeasures$ = hydrationMeasures[0],
+              startTime = _hydrationMeasures$.startTime,
+              duration = _hydrationMeasures$.duration
+            var hydrateEnd = startTime + duration
+
+            if (event.timeStamp > hydrateEnd) {
+              onPerfEntry({
+                name: 'first-input-delay-after-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+              onPerfEntry({
+                name: 'time-to-first-input-after-hydration',
+                startTime: hydrateEnd,
+                value: event.timeStamp - hydrateEnd,
+              })
+              clearMeasures()
+            } else {
+              onPerfEntry({
+                name: 'first-input-delay-before-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+            }
+          }
+        })
+      }
+
+      /***/
+    },
+
     /***/ yLiY: /***/ function(module, exports, __webpack_require__) {
       'use strict'
Diff for main-HASH.module.js
@@ -418,18 +418,16 @@
             }
 
             var { page: app, mod } = yield pageLoader.loadPageScript('/_app')
-            App = app
+            App = app // if (mod && mod.unstable_onPerformanceData) {
+            //   onPerfEntry = function({ name, startTime, value, duration }) {
+            //     mod.unstable_onPerformanceData({ name, startTime, value, duration })
+            //   }
+            // }
+
+            onPerfEntry = false
 
             if (mod && mod.unstable_onPerformanceData) {
-              onPerfEntry = function onPerfEntry(_ref3) {
-                var { name, startTime, value, duration } = _ref3
-                mod.unstable_onPerformanceData({
-                  name,
-                  startTime,
-                  value,
-                  duration,
-                })
-              }
+              onPerfEntry = false
             }
 
             var initialErr = err
@@ -460,8 +458,8 @@
                 Component,
                 wrapApp,
                 err: initialErr,
-                subscription: (_ref4, App) => {
-                  var { Component, props, err } = _ref4
+                subscription: (_ref3, App) => {
+                  var { Component, props, err } = _ref3
                   render({
                     App,
                     Component,
@@ -613,8 +611,23 @@
           'beforeRender'
         )
         performance.measure('Next.js-hydration', 'beforeRender', 'afterHydrate')
+        console.log(onPerfEntry, 'eve')
 
         if (onPerfEntry) {
+          console.log('noteven')
+
+          _promise.default
+            .resolve()
+            .then(() =>
+              (0, _interopRequireWildcard2.default)(__webpack_require__('dT2I'))
+            )
+            .then(mod => {
+              mod.default(onPerfEntry)
+            })
+            .catch(err => {
+              console.error('Error measuring FID', err)
+            })
+
           performance.getEntriesByName('Next.js-hydration').forEach(onPerfEntry)
           performance.getEntriesByName('beforeRender').forEach(onPerfEntry)
         }
@@ -656,16 +669,10 @@
         ;['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(
           mark => performance.clearMarks(mark)
         )
-        ;[
-          'Next.js-before-hydration',
-          'Next.js-hydration',
-          'Next.js-route-change-to-render',
-          'Next.js-render',
-        ].forEach(measure => performance.clearMeasures(measure))
       }
 
-      function AppContainer(_ref5) {
-        var { children } = _ref5
+      function AppContainer(_ref4) {
+        var { children } = _ref4
         return _react.default.createElement(
           Container,
           {
@@ -709,8 +716,8 @@
       }
 
       function _doRender() {
-        _doRender = (0, _asyncToGenerator2.default)(function*(_ref6) {
-          var { App, Component, props, err } = _ref6 // Usual getInitialProps fetching is handled in next/router
+        _doRender = (0, _asyncToGenerator2.default)(function*(_ref5) {
+          var { App, Component, props, err } = _ref5 // Usual getInitialProps fetching is handled in next/router
           // this is for when ErrorComponent gets replaced by Component by HMR
 
           if (
@@ -770,6 +777,63 @@
       /***/
     },
 
+    /***/ dT2I: /***/ function(module, exports, __webpack_require__) {
+      'use strict'
+
+      /* global hydrationMetrics */
+
+      var _Object$defineProperty = __webpack_require__('hfKm')
+
+      _Object$defineProperty(exports, '__esModule', {
+        value: true,
+      })
+
+      exports.default = onPerfEntry => {
+        function clearMeasures() {
+          ;[
+            'Next.js-hydration',
+            'Next.js-before-hydration',
+            'Next.js-route-change-to-render',
+            'Next.js-render',
+          ].forEach(measure => performance.clearMeasures(measure))
+        }
+
+        hydrationMetrics.onInputDelay((delay, event) => {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+
+          if (hydrationMeasures.length > 0) {
+            var { startTime, duration } = hydrationMeasures[0]
+            var hydrateEnd = startTime + duration
+
+            if (event.timeStamp > hydrateEnd) {
+              onPerfEntry({
+                name: 'first-input-delay-after-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+              onPerfEntry({
+                name: 'time-to-first-input-after-hydration',
+                startTime: hydrateEnd,
+                value: event.timeStamp - hydrateEnd,
+              })
+              clearMeasures()
+            } else {
+              onPerfEntry({
+                name: 'first-input-delay-before-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+            }
+          }
+        })
+      }
+
+      /***/
+    },
+
     /***/ yLiY: /***/ function(module, exports, __webpack_require__) {
       'use strict'
Diff for index.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-6c5edf42e2a83b8e821d.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next">Hello world 👋</div>
@@ -163,13 +265,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-72e4ccc4092f95056ddd.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-6c5edf42e2a83b8e821d.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-6c5edf42e2a83b8e821d.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next">
@@ -168,13 +270,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-72e4ccc4092f95056ddd.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-6c5edf42e2a83b8e821d.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-6c5edf42e2a83b8e821d.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next"><div>I use withRouter</div></div>
@@ -163,13 +265,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-72e4ccc4092f95056ddd.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-6c5edf42e2a83b8e821d.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 15.1s 15.2s ⚠️ +64ms
nodeModulesSize 48.9 MB 48.9 MB ⚠️ +10.8 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js gzip 5.11 kB 5.28 kB ⚠️ +167 B
webpack-HASH.js gzip 746 B 746 B
4952ddcd88e7..54d3.js gzip 4.68 kB 4.68 kB
commons.HASH.js gzip 4.06 kB 4.06 kB
de003c3a9d30..9881.js gzip 13.7 kB 13.7 kB
framework.HASH.js gzip 39.1 kB 39.1 kB
Overall change 67.4 kB 67.6 kB ⚠️ +167 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js gzip 4.17 kB 4.34 kB ⚠️ +174 B
webpack-HASH..dule.js gzip 746 B 746 B
4952ddcd88e7..dule.js gzip 5.56 kB 5.56 kB
de003c3a9d30..dule.js gzip 12.5 kB 12.5 kB
framework.HA..dule.js gzip 39.1 kB 39.1 kB
Overall change 62.1 kB 62.2 kB ⚠️ +174 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js gzip 4.76 kB 4.76 kB
Overall change 4.76 kB 4.76 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js gzip 1.33 kB 1.33 kB
_error.js gzip 4.07 kB 4.07 kB
hooks.js gzip 779 B 779 B
index.js gzip 222 B 222 B
link.js gzip 2.9 kB 2.9 kB
routerDirect.js gzip 283 B 283 B
withRouter.js gzip 282 B 282 B
Overall change 9.87 kB 9.87 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js gzip 757 B 757 B
_error.module.js gzip 3.06 kB 3.06 kB
hooks.module.js gzip 371 B 371 B
index.module.js gzip 212 B 212 B
link.module.js gzip 2.47 kB 2.47 kB
routerDirect..dule.js gzip 273 B 273 B
withRouter.m..dule.js gzip 272 B 272 B
Overall change 7.41 kB 7.41 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js gzip 61 B 61 B
_buildManife..dule.js gzip 61 B 61 B
Overall change 122 B 122 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js gzip 77.8 kB 78.6 kB ⚠️ +833 B
hooks.html gzip 1.05 kB 1.85 kB ⚠️ +795 B
index.js gzip 78 kB 78.8 kB ⚠️ +836 B
link.js gzip 80.4 kB 81.2 kB ⚠️ +844 B
routerDirect.js gzip 78 kB 78.9 kB ⚠️ +845 B
withRouter.js gzip 78.1 kB 78.9 kB ⚠️ +833 B
Overall change 393 kB 398 kB ⚠️ +4.99 kB

Commit: 70f6c79

@ijjk
Copy link
Member

ijjk commented Jan 8, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 14.2s 13.8s -388ms
nodeModulesSize 48.9 MB 48.9 MB ⚠️ +10.5 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js gzip 5.11 kB 5.29 kB ⚠️ +179 B
webpack-HASH.js gzip 746 B 746 B
4952ddcd88e7..54d3.js gzip 4.68 kB 4.68 kB
commons.HASH.js gzip 4.06 kB 4.06 kB
de003c3a9d30..9881.js gzip 13.7 kB 13.7 kB
framework.HASH.js gzip 39.1 kB 39.1 kB
Overall change 67.4 kB 67.6 kB ⚠️ +179 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js gzip 4.17 kB 4.35 kB ⚠️ +176 B
webpack-HASH..dule.js gzip 746 B 746 B
4952ddcd88e7..dule.js gzip 5.56 kB 5.56 kB
de003c3a9d30..dule.js gzip 12.5 kB 12.5 kB
framework.HA..dule.js gzip 39.1 kB 39.1 kB
Overall change 62.1 kB 62.2 kB ⚠️ +176 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js gzip 4.76 kB 4.76 kB
Overall change 4.76 kB 4.76 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js gzip 1.33 kB 1.33 kB
_error.js gzip 4.07 kB 4.07 kB
hooks.js gzip 779 B 779 B
index.js gzip 222 B 222 B
link.js gzip 2.9 kB 2.9 kB
routerDirect.js gzip 283 B 283 B
withRouter.js gzip 282 B 282 B
Overall change 9.87 kB 9.87 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js gzip 757 B 757 B
_error.module.js gzip 3.06 kB 3.06 kB
hooks.module.js gzip 371 B 371 B
index.module.js gzip 212 B 212 B
link.module.js gzip 2.47 kB 2.47 kB
routerDirect..dule.js gzip 273 B 273 B
withRouter.m..dule.js gzip 272 B 272 B
Overall change 7.41 kB 7.41 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js gzip 61 B 61 B
_buildManife..dule.js gzip 61 B 61 B
Overall change 122 B 122 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html gzip 1.02 kB 1.82 kB ⚠️ +799 B
link.html gzip 1.03 kB 1.83 kB ⚠️ +799 B
withRouter.html gzip 1.01 kB 1.81 kB ⚠️ +800 B
Overall change 3.06 kB 5.46 kB ⚠️ +2.4 kB

Diffs

Diff for main-HASH.js
@@ -901,6 +901,19 @@
         performance.measure('Next.js-hydration', 'beforeRender', 'afterHydrate')
 
         if (onPerfEntry) {
+          _promise['default']
+            .resolve()
+            .then(function() {
+              return (0,
+              _interopRequireWildcard2['default'])(__webpack_require__('dT2I'))
+            })
+            .then(function(mod) {
+              mod['default'](onPerfEntry)
+            })
+            ['catch'](function(err) {
+              console.error('Error measuring FID', err)
+            })
+
           performance.getEntriesByName('Next.js-hydration').forEach(onPerfEntry)
           performance.getEntriesByName('beforeRender').forEach(onPerfEntry)
         }
@@ -944,14 +957,6 @@
             return performance.clearMarks(mark)
           }
         )
-        ;[
-          'Next.js-before-hydration',
-          'Next.js-hydration',
-          'Next.js-route-change-to-render',
-          'Next.js-render',
-        ].forEach(function(measure) {
-          return performance.clearMeasures(measure)
-        })
       }
 
       function AppContainer(_ref5) {
@@ -1174,6 +1179,67 @@
       /***/
     },
 
+    /***/ dT2I: /***/ function(module, exports, __webpack_require__) {
+      'use strict'
+
+      /* global hydrationMetrics */
+
+      var _Object$defineProperty = __webpack_require__('hfKm')
+
+      _Object$defineProperty(exports, '__esModule', {
+        value: true,
+      })
+
+      exports['default'] = function(onPerfEntry) {
+        function clearMeasures() {
+          ;[
+            'Next.js-hydration',
+            'Next.js-before-hydration',
+            'Next.js-route-change-to-render',
+            'Next.js-render',
+          ].forEach(function(measure) {
+            return performance.clearMeasures(measure)
+          })
+        }
+
+        hydrationMetrics.onInputDelay(function(delay, event) {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+
+          if (hydrationMeasures.length > 0) {
+            var _hydrationMeasures$ = hydrationMeasures[0],
+              startTime = _hydrationMeasures$.startTime,
+              duration = _hydrationMeasures$.duration
+            var hydrateEnd = startTime + duration
+
+            if (event.timeStamp > hydrateEnd) {
+              onPerfEntry({
+                name: 'first-input-delay-after-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+              onPerfEntry({
+                name: 'time-to-first-input-after-hydration',
+                startTime: hydrateEnd,
+                value: event.timeStamp - hydrateEnd,
+              })
+              clearMeasures()
+            } else {
+              onPerfEntry({
+                name: 'first-input-delay-before-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+            }
+          }
+        })
+      }
+
+      /***/
+    },
+
     /***/ yLiY: /***/ function(module, exports, __webpack_require__) {
       'use strict'
Diff for main-HASH.module.js
@@ -615,6 +615,18 @@
         performance.measure('Next.js-hydration', 'beforeRender', 'afterHydrate')
 
         if (onPerfEntry) {
+          _promise.default
+            .resolve()
+            .then(() =>
+              (0, _interopRequireWildcard2.default)(__webpack_require__('dT2I'))
+            )
+            .then(mod => {
+              mod.default(onPerfEntry)
+            })
+            .catch(err => {
+              console.error('Error measuring FID', err)
+            })
+
           performance.getEntriesByName('Next.js-hydration').forEach(onPerfEntry)
           performance.getEntriesByName('beforeRender').forEach(onPerfEntry)
         }
@@ -656,12 +668,6 @@
         ;['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(
           mark => performance.clearMarks(mark)
         )
-        ;[
-          'Next.js-before-hydration',
-          'Next.js-hydration',
-          'Next.js-route-change-to-render',
-          'Next.js-render',
-        ].forEach(measure => performance.clearMeasures(measure))
       }
 
       function AppContainer(_ref5) {
@@ -770,6 +776,63 @@
       /***/
     },
 
+    /***/ dT2I: /***/ function(module, exports, __webpack_require__) {
+      'use strict'
+
+      /* global hydrationMetrics */
+
+      var _Object$defineProperty = __webpack_require__('hfKm')
+
+      _Object$defineProperty(exports, '__esModule', {
+        value: true,
+      })
+
+      exports.default = onPerfEntry => {
+        function clearMeasures() {
+          ;[
+            'Next.js-hydration',
+            'Next.js-before-hydration',
+            'Next.js-route-change-to-render',
+            'Next.js-render',
+          ].forEach(measure => performance.clearMeasures(measure))
+        }
+
+        hydrationMetrics.onInputDelay((delay, event) => {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+
+          if (hydrationMeasures.length > 0) {
+            var { startTime, duration } = hydrationMeasures[0]
+            var hydrateEnd = startTime + duration
+
+            if (event.timeStamp > hydrateEnd) {
+              onPerfEntry({
+                name: 'first-input-delay-after-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+              onPerfEntry({
+                name: 'time-to-first-input-after-hydration',
+                startTime: hydrateEnd,
+                value: event.timeStamp - hydrateEnd,
+              })
+              clearMeasures()
+            } else {
+              onPerfEntry({
+                name: 'first-input-delay-before-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+            }
+          }
+        })
+      }
+
+      /***/
+    },
+
     /***/ yLiY: /***/ function(module, exports, __webpack_require__) {
       'use strict'
Diff for index.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-6a144831c76ea3de200f.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next">Hello world 👋</div>
@@ -163,13 +265,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-7dff2e8b92a3909dd91b.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-6a144831c76ea3de200f.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-6a144831c76ea3de200f.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next">
@@ -168,13 +270,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-7dff2e8b92a3909dd91b.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-6a144831c76ea3de200f.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-6a144831c76ea3de200f.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next"><div>I use withRouter</div></div>
@@ -163,13 +265,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-7dff2e8b92a3909dd91b.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-6a144831c76ea3de200f.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 14.5s 14s -440ms
nodeModulesSize 48.9 MB 48.9 MB ⚠️ +10.5 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js gzip 5.11 kB 5.29 kB ⚠️ +179 B
webpack-HASH.js gzip 746 B 746 B
4952ddcd88e7..54d3.js gzip 4.68 kB 4.68 kB
commons.HASH.js gzip 4.06 kB 4.06 kB
de003c3a9d30..9881.js gzip 13.7 kB 13.7 kB
framework.HASH.js gzip 39.1 kB 39.1 kB
Overall change 67.4 kB 67.6 kB ⚠️ +179 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js gzip 4.17 kB 4.35 kB ⚠️ +176 B
webpack-HASH..dule.js gzip 746 B 746 B
4952ddcd88e7..dule.js gzip 5.56 kB 5.56 kB
de003c3a9d30..dule.js gzip 12.5 kB 12.5 kB
framework.HA..dule.js gzip 39.1 kB 39.1 kB
Overall change 62.1 kB 62.2 kB ⚠️ +176 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js gzip 4.76 kB 4.76 kB
Overall change 4.76 kB 4.76 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js gzip 1.33 kB 1.33 kB
_error.js gzip 4.07 kB 4.07 kB
hooks.js gzip 779 B 779 B
index.js gzip 222 B 222 B
link.js gzip 2.9 kB 2.9 kB
routerDirect.js gzip 283 B 283 B
withRouter.js gzip 282 B 282 B
Overall change 9.87 kB 9.87 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js gzip 757 B 757 B
_error.module.js gzip 3.06 kB 3.06 kB
hooks.module.js gzip 371 B 371 B
index.module.js gzip 212 B 212 B
link.module.js gzip 2.47 kB 2.47 kB
routerDirect..dule.js gzip 273 B 273 B
withRouter.m..dule.js gzip 272 B 272 B
Overall change 7.41 kB 7.41 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js gzip 61 B 61 B
_buildManife..dule.js gzip 61 B 61 B
Overall change 122 B 122 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js gzip 77.8 kB 78.6 kB ⚠️ +832 B
hooks.html gzip 1.05 kB 1.85 kB ⚠️ +795 B
index.js gzip 78 kB 78.8 kB ⚠️ +834 B
link.js gzip 80.4 kB 81.2 kB ⚠️ +840 B
routerDirect.js gzip 78 kB 78.9 kB ⚠️ +843 B
withRouter.js gzip 78.1 kB 78.9 kB ⚠️ +831 B
Overall change 393 kB 398 kB ⚠️ +4.97 kB

Commit: 0d4d039

@ijjk
Copy link
Member

ijjk commented Jan 8, 2020

Stats from current PR

Default Server Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 13.1s 13.3s ⚠️ +134ms
nodeModulesSize 48.9 MB 48.9 MB ⚠️ +10.4 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js gzip 5.11 kB 5.29 kB ⚠️ +186 B
webpack-HASH.js gzip 746 B 746 B
4952ddcd88e7..54d3.js gzip 4.68 kB 4.68 kB
commons.HASH.js gzip 4.06 kB 4.06 kB
de003c3a9d30..9881.js gzip 13.7 kB 13.7 kB
framework.HASH.js gzip 39.1 kB 39.1 kB
Overall change 67.4 kB 67.6 kB ⚠️ +186 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js gzip 4.17 kB 4.35 kB ⚠️ +179 B
webpack-HASH..dule.js gzip 746 B 746 B
4952ddcd88e7..dule.js gzip 5.56 kB 5.56 kB
de003c3a9d30..dule.js gzip 12.5 kB 12.5 kB
framework.HA..dule.js gzip 39.1 kB 39.1 kB
Overall change 62.1 kB 62.2 kB ⚠️ +179 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js gzip 4.76 kB 4.76 kB
Overall change 4.76 kB 4.76 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js gzip 1.33 kB 1.33 kB
_error.js gzip 4.07 kB 4.07 kB
hooks.js gzip 779 B 779 B
index.js gzip 222 B 222 B
link.js gzip 2.9 kB 2.9 kB
routerDirect.js gzip 283 B 283 B
withRouter.js gzip 282 B 282 B
Overall change 9.87 kB 9.87 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js gzip 757 B 757 B
_error.module.js gzip 3.06 kB 3.06 kB
hooks.module.js gzip 371 B 371 B
index.module.js gzip 212 B 212 B
link.module.js gzip 2.47 kB 2.47 kB
routerDirect..dule.js gzip 273 B 273 B
withRouter.m..dule.js gzip 272 B 272 B
Overall change 7.41 kB 7.41 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js gzip 61 B 61 B
_buildManife..dule.js gzip 61 B 61 B
Overall change 122 B 122 B
Rendered Page Sizes Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
index.html gzip 1.02 kB 1.82 kB ⚠️ +800 B
link.html gzip 1.03 kB 1.83 kB ⚠️ +800 B
withRouter.html gzip 1.01 kB 1.82 kB ⚠️ +801 B
Overall change 3.06 kB 5.47 kB ⚠️ +2.4 kB

Diffs

Diff for main-HASH.js
@@ -901,6 +901,19 @@
         performance.measure('Next.js-hydration', 'beforeRender', 'afterHydrate')
 
         if (onPerfEntry) {
+          _promise['default']
+            .resolve()
+            .then(function() {
+              return (0,
+              _interopRequireWildcard2['default'])(__webpack_require__('dT2I'))
+            })
+            .then(function(mod) {
+              mod['default'](onPerfEntry)
+            })
+            ['catch'](function(err) {
+              console.error('Error measuring FID', err)
+            })
+
           performance.getEntriesByName('Next.js-hydration').forEach(onPerfEntry)
           performance.getEntriesByName('beforeRender').forEach(onPerfEntry)
         }
@@ -936,6 +949,11 @@
         }
 
         clearMarks()
+        ;['Next.js-route-change-to-render', 'Next.js-render'].forEach(function(
+          measure
+        ) {
+          return performance.clearMeasures(measure)
+        })
       }
 
       function clearMarks() {
@@ -944,14 +962,6 @@
             return performance.clearMarks(mark)
           }
         )
-        ;[
-          'Next.js-before-hydration',
-          'Next.js-hydration',
-          'Next.js-route-change-to-render',
-          'Next.js-render',
-        ].forEach(function(measure) {
-          return performance.clearMeasures(measure)
-        })
       }
 
       function AppContainer(_ref5) {
@@ -1174,6 +1184,60 @@
       /***/
     },
 
+    /***/ dT2I: /***/ function(module, exports, __webpack_require__) {
+      'use strict'
+
+      /* global hydrationMetrics */
+
+      var _Object$defineProperty = __webpack_require__('hfKm')
+
+      _Object$defineProperty(exports, '__esModule', {
+        value: true,
+      })
+
+      exports['default'] = function(onPerfEntry) {
+        hydrationMetrics.onInputDelay(function(delay, event) {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+
+          if (hydrationMeasures.length > 0) {
+            var _hydrationMeasures$ = hydrationMeasures[0],
+              startTime = _hydrationMeasures$.startTime,
+              duration = _hydrationMeasures$.duration
+            var hydrateEnd = startTime + duration
+
+            if (event.timeStamp > hydrateEnd) {
+              onPerfEntry({
+                name: 'first-input-delay-after-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+              onPerfEntry({
+                name: 'time-to-first-input-after-hydration',
+                startTime: hydrateEnd,
+                value: event.timeStamp - hydrateEnd,
+              })
+              ;['Next.js-hydration', 'Next.js-before-hydration'].forEach(
+                function(measure) {
+                  return performance.clearMeasures(measure)
+                }
+              )
+            } else {
+              onPerfEntry({
+                name: 'first-input-delay-before-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+            }
+          }
+        })
+      }
+
+      /***/
+    },
+
     /***/ yLiY: /***/ function(module, exports, __webpack_require__) {
       'use strict'
Diff for main-HASH.module.js
@@ -615,6 +615,18 @@
         performance.measure('Next.js-hydration', 'beforeRender', 'afterHydrate')
 
         if (onPerfEntry) {
+          _promise.default
+            .resolve()
+            .then(() =>
+              (0, _interopRequireWildcard2.default)(__webpack_require__('dT2I'))
+            )
+            .then(mod => {
+              mod.default(onPerfEntry)
+            })
+            .catch(err => {
+              console.error('Error measuring FID', err)
+            })
+
           performance.getEntriesByName('Next.js-hydration').forEach(onPerfEntry)
           performance.getEntriesByName('beforeRender').forEach(onPerfEntry)
         }
@@ -650,18 +662,15 @@
         }
 
         clearMarks()
+        ;['Next.js-route-change-to-render', 'Next.js-render'].forEach(measure =>
+          performance.clearMeasures(measure)
+        )
       }
 
       function clearMarks() {
         ;['beforeRender', 'afterHydrate', 'afterRender', 'routeChange'].forEach(
           mark => performance.clearMarks(mark)
         )
-        ;[
-          'Next.js-before-hydration',
-          'Next.js-hydration',
-          'Next.js-route-change-to-render',
-          'Next.js-render',
-        ].forEach(measure => performance.clearMeasures(measure))
       }
 
       function AppContainer(_ref5) {
@@ -770,6 +779,56 @@
       /***/
     },
 
+    /***/ dT2I: /***/ function(module, exports, __webpack_require__) {
+      'use strict'
+
+      /* global hydrationMetrics */
+
+      var _Object$defineProperty = __webpack_require__('hfKm')
+
+      _Object$defineProperty(exports, '__esModule', {
+        value: true,
+      })
+
+      exports.default = onPerfEntry => {
+        hydrationMetrics.onInputDelay((delay, event) => {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+
+          if (hydrationMeasures.length > 0) {
+            var { startTime, duration } = hydrationMeasures[0]
+            var hydrateEnd = startTime + duration
+
+            if (event.timeStamp > hydrateEnd) {
+              onPerfEntry({
+                name: 'first-input-delay-after-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+              onPerfEntry({
+                name: 'time-to-first-input-after-hydration',
+                startTime: hydrateEnd,
+                value: event.timeStamp - hydrateEnd,
+              })
+              ;['Next.js-hydration', 'Next.js-before-hydration'].forEach(
+                measure => performance.clearMeasures(measure)
+              )
+            } else {
+              onPerfEntry({
+                name: 'first-input-delay-before-hydration',
+                startTime: event.timeStamp,
+                value: delay,
+              })
+            }
+          }
+        })
+      }
+
+      /***/
+    },
+
     /***/ yLiY: /***/ function(module, exports, __webpack_require__) {
       'use strict'
Diff for index.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-eabaa986b1455a8ca71c.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next">Hello world 👋</div>
@@ -163,13 +265,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-d8454a7ccf614c06d7f1.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-eabaa986b1455a8ca71c.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"
Diff for link.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-eabaa986b1455a8ca71c.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next">
@@ -168,13 +270,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-d8454a7ccf614c06d7f1.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-eabaa986b1455a8ca71c.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"
Diff for withRouter.html
@@ -45,10 +45,112 @@
     />
     <link
       rel="preload"
-      href="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      href="/_next/static/runtime/main-eabaa986b1455a8ca71c.module.js"
       as="script"
       crossorigin="anonymous"
     />
+    <script>
+      ;(function fidPolyfill(addEventListener, removeEventListener) {
+        var firstInputEvent
+        var firstInputDelay
+        var firstInputTimeStamp
+        var callbacks = []
+        var listenerOpts = {
+          passive: true,
+          capture: true,
+        }
+        var startTimeStamp = +new Date()
+        var pointerup = 'pointerup'
+        var pointercancel = 'pointercancel'
+
+        function onInputDelay(callback) {
+          callbacks.push(callback)
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function recordInputDelay(delay, evt) {
+          firstInputEvent = evt
+          firstInputDelay = delay
+          firstInputTimeStamp = +new Date()
+          reportInputDelayIfRecordedAndValid()
+        }
+
+        function reportInputDelayIfRecordedAndValid() {
+          var hydrationMeasures = performance.getEntriesByName(
+            'Next.js-hydration',
+            'measure'
+          )
+          var firstInputStart = firstInputTimeStamp - startTimeStamp
+
+          if (
+            firstInputDelay >= 0 &&
+            firstInputDelay < firstInputStart &&
+            (hydrationMeasures.length === 0 ||
+              hydrationMeasures[0].startTime < firstInputStart)
+          ) {
+            callbacks.forEach(function(callback) {
+              callback(firstInputDelay, firstInputEvent)
+            }) // If the app is already hydrated, that means the first "post-hydration" input
+            // has been measured and listeners can be removed
+
+            if (hydrationMeasures.length > 0) {
+              eachEventType(removeEventListener)
+              callbacks = []
+            }
+          }
+        }
+
+        function onPointerDown(delay, evt) {
+          function onPointerUp() {
+            recordInputDelay(delay, evt)
+          }
+
+          function onPointerCancel() {
+            removePointerEventListeners()
+          }
+
+          function removePointerEventListeners() {
+            removeEventListener(pointerup, onPointerUp, listenerOpts)
+            removeEventListener(pointercancel, onPointerCancel, listenerOpts)
+          }
+
+          addEventListener(pointerup, onPointerUp, listenerOpts)
+          addEventListener(pointercancel, onPointerCancel, listenerOpts)
+        }
+
+        function onInput(evt) {
+          if (evt.cancelable) {
+            var isEpochTime = evt.timeStamp > 1e12
+            var now = isEpochTime ? +new Date() : performance.now()
+            var delay = now - evt.timeStamp
+
+            if (evt.type === 'pointerdown') {
+              onPointerDown(delay, evt)
+            } else {
+              recordInputDelay(delay, evt)
+            }
+          }
+        }
+
+        function eachEventType(callback) {
+          var eventTypes = [
+            'click',
+            'mousedown',
+            'keydown',
+            'touchstart',
+            'pointerdown',
+          ]
+          eventTypes.forEach(function(eventType) {
+            callback(eventType, onInput, listenerOpts)
+          })
+        }
+
+        eachEventType(addEventListener)
+        var context = self
+        context['hydrationMetrics'] = context['hydrationMetrics'] || {}
+        context['hydrationMetrics']['onInputDelay'] = onInputDelay
+      })(addEventListener, removeEventListener)
+    </script>
   </head>
   <body>
     <div id="__next"><div>I use withRouter</div></div>
@@ -163,13 +265,13 @@
       type="module"
     ></script
     ><script
-      src="/_next/static/runtime/main-20e26367cdbf3cd02b09.js"
+      src="/_next/static/runtime/main-d8454a7ccf614c06d7f1.js"
       defer=""
       crossorigin="anonymous"
       nomodule=""
     ></script
     ><script
-      src="/_next/static/runtime/main-c3e25200bb4ca113c514.module.js"
+      src="/_next/static/runtime/main-eabaa986b1455a8ca71c.module.js"
       defer=""
       crossorigin="anonymous"
       type="module"

Serverless Mode (Increase detected ⚠️)
General Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
buildDuration 13.4s 13.7s ⚠️ +231ms
nodeModulesSize 48.9 MB 48.9 MB ⚠️ +10.4 kB
Client Bundles (main, webpack, commons) Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.js gzip 5.11 kB 5.29 kB ⚠️ +186 B
webpack-HASH.js gzip 746 B 746 B
4952ddcd88e7..54d3.js gzip 4.68 kB 4.68 kB
commons.HASH.js gzip 4.06 kB 4.06 kB
de003c3a9d30..9881.js gzip 13.7 kB 13.7 kB
framework.HASH.js gzip 39.1 kB 39.1 kB
Overall change 67.4 kB 67.6 kB ⚠️ +186 B
Client Bundles (main, webpack, commons) Modern Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
main-HASH.module.js gzip 4.17 kB 4.35 kB ⚠️ +179 B
webpack-HASH..dule.js gzip 746 B 746 B
4952ddcd88e7..dule.js gzip 5.56 kB 5.56 kB
de003c3a9d30..dule.js gzip 12.5 kB 12.5 kB
framework.HA..dule.js gzip 39.1 kB 39.1 kB
Overall change 62.1 kB 62.2 kB ⚠️ +179 B
Legacy Client Bundles (polyfills)
zeit/next.js canary azukaru/next.js first-input-delay Change
polyfills-HASH.js gzip 4.76 kB 4.76 kB
Overall change 4.76 kB 4.76 kB
Client Pages
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.js gzip 1.33 kB 1.33 kB
_error.js gzip 4.07 kB 4.07 kB
hooks.js gzip 779 B 779 B
index.js gzip 222 B 222 B
link.js gzip 2.9 kB 2.9 kB
routerDirect.js gzip 283 B 283 B
withRouter.js gzip 282 B 282 B
Overall change 9.87 kB 9.87 kB
Client Pages Modern
zeit/next.js canary azukaru/next.js first-input-delay Change
_app.module.js gzip 757 B 757 B
_error.module.js gzip 3.06 kB 3.06 kB
hooks.module.js gzip 371 B 371 B
index.module.js gzip 212 B 212 B
link.module.js gzip 2.47 kB 2.47 kB
routerDirect..dule.js gzip 273 B 273 B
withRouter.m..dule.js gzip 272 B 272 B
Overall change 7.41 kB 7.41 kB
Client Build Manifests
zeit/next.js canary azukaru/next.js first-input-delay Change
_buildManifest.js gzip 61 B 61 B
_buildManife..dule.js gzip 61 B 61 B
Overall change 122 B 122 B
Serverless bundles Overall increase ⚠️
zeit/next.js canary azukaru/next.js first-input-delay Change
_error.js gzip 77.8 kB 78.6 kB ⚠️ +834 B
hooks.html gzip 1.05 kB 1.85 kB ⚠️ +795 B
index.js gzip 78 kB 78.8 kB ⚠️ +837 B
link.js gzip 80.4 kB 81.2 kB ⚠️ +844 B
routerDirect.js gzip 78 kB 78.9 kB ⚠️ +845 B
withRouter.js gzip 78.1 kB 78.9 kB ⚠️ +833 B
Overall change 393 kB 398 kB ⚠️ +4.99 kB

Commit: b7fe05a

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants