Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Load third party iframe in React after page load, so that the iframe does not affect PageSpeed score

I have an iframe that loads a third party widget. I only want to display this iframe after my page has loaded, because I don't want to slow down my page load speed. I followed a medium article which describes how to do this, but their solution doesn't work because the onload function, finishLoading, is never called

export default ({src, width, height}) => {

  const [loading, stillLoading] = useState(true)
  const finishLoading = () => {
      alert('finished loading')
      stillLoading(false)
  }
  ...
  return(
    {loading ? '' : 
      <iframe
        src={src}
        width={width}
        height={height}
        scrolling="no"
        onLoad={finishLoading}
      >
        className={`tpwidget flex-center-row`}>
      </iframe>
    }
  )
}

Update

By using useEffect, I can get the iframe to load after everything else(theoretically) but I find that removing the iframe completely improves my PageSpeed score, and that just loading the iframe after(using useEffect) doesn't have much of a positive effect on PageSpeed.


If it helps, the domain is suddenlysask.com and the third party widget is the amazon ads.

like image 954
Sam Avatar asked Jul 13 '20 07:07

Sam


2 Answers

Update

I visited the website and I'm sure you are using Gatsby. Gatsby is using SSR and React.lazy and Suspense are not yet available for server-side rendering. If you want to do code-splitting in a server-rendered app, Loadable Components is recommended in React docs. It has a nice guide for bundle splitting with server-side rendering.

There is a gatsby plugin to make your life easier gatsby-plugin-loadable-components-ssr. After you install and configure the plugin you can use loadable like this:

AmazonFrame.js

import React from "react";

const AmazonFrame = ({ src, width, height }) => (
  <iframe src={src} width={width} height={height} scrolling="no"></iframe>
);

App.js

import React from "react";
import loadable from "@loadable/component";

const AmazonFrame = loadable(() => import("./AmazonFrame"), {
  fallback: <div>Loading...</div>
});

function App() {
  return (
    <div>
      <AmazonFrame src="src" width="100%" height="200px" />
    </div>
  );
}

export default App;

or

import React from "react";
import loadable from "@loadable/component";

const AmazonFrame = loadable(() => import("./AmazonFrame"));

function App() {
  return (
    <div>
      <AmazonFrame fallback={<div>Loading...</div>} />
    </div>
  );
}

export default App;

Original answer

You need to use Code-Splitting. Code-Splitting is a feature supported by bundlers like Webpack, Rollup, and Browserify (via factor-bundle) which can create multiple bundles that can be dynamically loaded at runtime.

If you’re using Create React App, this is already configured for you and you can start using it immediately.

Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. While you haven’t reduced the overall amount of code in your app, you’ve avoided loading code that the user may never need, and reduced the amount of code needed during the initial load.

Here is an example solution to your problem which will lazy load the Amazon ads iframe so it won't be loaded with your initial bundle:

AmazonFrame.js

import React from "react";

const AmazonFrame = ({ src, width, height }) => (
  <iframe src={src} width={width} height={height} scrolling="no"></iframe>
);

export default AmazonFrame;

App.js

import React, { Suspense, lazy } from "react";

// React.lazy takes a function that must call a dynamic import(). This must return a Promise
// which resolves to a module with a default export containing a React component.
const AmazonFrame = lazy(() => import("./AmazonFrame"));
function App() {
  return (
    <div>
      {/* The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback
       content (such as a loading indicator) while we’re waiting for the lazy component to load */}
      {/* The fallback prop accepts any React elements that you want to render while waiting for the component to load */}
      <Suspense fallback={<div>Loading...</div>}>
        <AmazonFrame src="src" width="100%" height="200px" />
      </Suspense>
    </div>
  );
}

export default App;
like image 75
Ahmed Mokhtar Avatar answered Sep 23 '22 01:09

Ahmed Mokhtar


Google PageSpeed/Lighthouse doesn't just analyze the static content of the site as it is sent from the server when evaluating performance. Since most of the techniques demonstrated here load the iframe as quickly as possible (i.e. as soon as the useEffect hook is called), you wind up incurring the CPU and network request during the page-speed measurement time period.

Here are a couple of ways you can reduce the impact this iframe will have on your score. It can be hard to say what the impact will be in advance since it is dependent on the connection/network and the server performance—you'll want to test and experiment to get the best results.

  1. Use loading="lazy" on the iframe. This will instruct the browser not to load it until it gets near the viewport (e.g. if it is below the fold it won't load at all until the user scrolls down).
  2. Set a timeout in useEffect to delay loading of the iframe by a fixed duration, reducing the likelihood that it will occur during your page-speed measurement window. E.g.:
    const [ready, setReady] = useState(false)
    useEffect(() => { setTimeout(() => { setReady(true) }, 3000) }, [])
    return (<iframe src={ready ? "/your/url" : "about:blank"} loading="lazy" />)
    
  3. Similar to #2, only listen for an interaction event (e.g. scroll or click) on the page and only load the ads once that event has occurred.
like image 23
coreyward Avatar answered Sep 23 '22 01:09

coreyward