Create-react-app: Add preload to script and link tags in production builds

Created on 24 Oct 2017  路  29Comments  路  Source: facebook/create-react-app

I want to preload styles and main script. But the current config generates the .html without preload support. So is there any way to make that happens?

up for grabs! medium enhancement

Most helpful comment

@muhammadtarek
I have the same issue, and as a temporary solution I've made a little node-script, which runs automatically after main build process finishes.

Here it is:

const fs = require('fs');
const pathToEntry = './build/index.html';
const bundlesRegExp = /\/static\/\w+\/\w+.[a-z0-9]+.\w{2,3}/g;
const splitBy = '</title>';

const builtHTMLContent = fs.readFileSync(pathToEntry).toString();
const links = builtHTMLContent.match(bundlesRegExp);
const parts = builtHTMLContent.split(splitBy);

let fileWithPreload = [
  parts[0],
  splitBy,
];

links.forEach(link => {
  let fileType = 'script';

  if (/\.css$/.test(link)) {
    fileType = 'style';
  }

  fileWithPreload = [
    ...fileWithPreload,
    `<link rel="preload" href="${link}" as="${fileType}">`,
  ];
});

fileWithPreload = [
  ...fileWithPreload,
  parts[1],
];

fs.writeFileSync(pathToEntry, fileWithPreload.join(''));

to run it automatically after each prod build add node ./addPreloadLink.js to npm script build like this:

{
  "scripts": {
    "build":  "react-scripts build && node ./addPreloadLinks.js"
  }
}

Hope it will help you 馃檪

All 29 comments

@muhammadtarek
I have the same issue, and as a temporary solution I've made a little node-script, which runs automatically after main build process finishes.

Here it is:

const fs = require('fs');
const pathToEntry = './build/index.html';
const bundlesRegExp = /\/static\/\w+\/\w+.[a-z0-9]+.\w{2,3}/g;
const splitBy = '</title>';

const builtHTMLContent = fs.readFileSync(pathToEntry).toString();
const links = builtHTMLContent.match(bundlesRegExp);
const parts = builtHTMLContent.split(splitBy);

let fileWithPreload = [
  parts[0],
  splitBy,
];

links.forEach(link => {
  let fileType = 'script';

  if (/\.css$/.test(link)) {
    fileType = 'style';
  }

  fileWithPreload = [
    ...fileWithPreload,
    `<link rel="preload" href="${link}" as="${fileType}">`,
  ];
});

fileWithPreload = [
  ...fileWithPreload,
  parts[1],
];

fs.writeFileSync(pathToEntry, fileWithPreload.join(''));

to run it automatically after each prod build add node ./addPreloadLink.js to npm script build like this:

{
  "scripts": {
    "build":  "react-scripts build && node ./addPreloadLinks.js"
  }
}

Hope it will help you 馃檪

It works. Thank you @necinc
Closing issue

Can you explain why this is useful? The JS/CSS bundle generated by CRA is required for the application to work, and there is nothing to display until it has loaded, so why would preload make any difference?

I need to request the JS/CSS bundle as soon as possible which will reduce the loading time that's as far I understand

@muhammadtarek
Actually yes, there is no reason to put preload link in the head of generated index.html, since the size of initial html is so small so reduction of loading time won't be tangible.
It's better to add preload to stream from your SSR.

@necinc Thanks, I didn't know that.

If I understand it right, "preload" is useful when you don't use a script/link right away but will likely need it in the future. Browser then optimistically starts loading it with a low priority.

This is different from CRA case where you can't possibly render anything without these tags.

It might, however, be useful for codesplit chunks.

You're right. I misunderstood it. Thanks for clarification

Hey all, just chiming in here. link rel preload is useful since it enables critical resources to start downloading before the page has finished parsing... As soon as the streaming HTML parser encounters this tag the browser knows to go ahead and start fetching the asset.

Putting link rel preload for critical CSS and JS can increase boot time on the order of 10+%.

Take for instance this page
image

The other script tags that are higher in the DOM can actually cause the critical main script to load later resulting is substandard perf on low quality connections.

One of the talks I have given on this topic:
https://youtu.be/RWLzUnESylc?t=579

--

Let me know if I can be of any help here!

Thanks as always.

In case of CRA the page is usually tiny (a hundred bytes at most) but I'd be open to supporting this if it's contributed upstream to html-webpack-plugin. Thanks for explaining!

Here's what I believe to be another use case:

I have an above-the-fold image that needs to be loaded as quickly as possible. Currently, the browser only knows to fetch the image _after_ the bundle containing the reference to that image has finished loading and parsing. Adding support for rel='preload' would allow me to begin loading that image more or less immediately.

@jacklenehan I don't think this is related to this issue? The issue is about generated script tags. I don't see how putting preload on them would make loading an image referenced from the bundle faster.

@gaearon sorry, my comment wasn't clear - I wanted to add rel='preload' to a <link> in the <head> of my page to tell the browser to start loading the relevant image as soon as possible. So e.g.

<head>
...
  <link rel="preload" href="foo.jpg" as="image">
...
</head>

where foo.jpg is an image that I know my bundle is going to ask for as soon as it's loaded and parsed, and will be the first thing the user sees. I suppose it might be possible to accomplish this by sticking foo.jpg in /static and adding <link rel="preload" href="%PUBLIC_URL%/foo.jpg" as="image">, but that feels hacky.

What is hacky about this? (Assuming you mean public/)

I think I don't understand what other solution you have in mind.

What is hacky about this? (Assuming you mean public/)
I think I don't understand what other solution you have in mind.

You would have to keep your resources in two places, manually adding the preload links when you reference assets in your code using import.

A perf boost of 10% (as @samccone said) with a webpack plugin like https://github.com/GoogleChromeLabs/preload-webpack-plugin should be a no brainer, imo. It should be part of best practices when building a React app.

Webpack 4 can do this:

import(/* webpackPrefetch: true */ "DashboardPage")

Another use case: preload font files in <head> before they're requested in the webpack-built CSS.

<link rel="preload" href="/static/media/some-font.4497aa9e.woff2" as="font" type="font/woff2" crossorigin>

I get dinged for not having this in the Chrome Lighthouse audit.

This might be more complicated than preloading .js assets though.

Script above creates malformed HTML appending </title><link rel="preload" href="/static/css/main.FOO.css" as="style"><link rel="preload" href="/static/js/main.BAR.js" as="script"> as a sibling to the <html> element. Heads up for anyone who stumbles here.

Hacked a fix together based on what's above..

// postbuild.js
const fs = require('fs');
const pathToEntry = './build/index.html';
const bundlesRegExp = /\/static\/\w+\/\w+.[a-z0-9]+.\w{2,3}/g;

const builtHTMLContent = fs.readFileSync(pathToEntry).toString();
const links = builtHTMLContent.match(bundlesRegExp);

const linkAs = {
  css: 'style',
  js: 'script'
}
const linkPreloads = links.map(link =>
  `<link rel="preload" as="${linkAs[link.split('.').pop()]}" href="${link}">`
).join('');

const htmlWithPreload = builtHTMLContent.replace(
  '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">',
  '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">' + linkPreloads
)

fs.writeFileSync(pathToEntry, htmlWithPreload);
{
  "scripts": {
    "build":  "react-scripts build && node ./postbuild.js"
  }
}

Be careful with how many things you preload. Chrome only makes 6 requests at a time. Great thread on preload from the Chrome Lighthouse audit crowd (+bonus Paul Irish). For anyone interested in a deeper dive on this topic: https://github.com/GoogleChrome/lighthouse/issues/3106

@necinc script has worked well for me 馃檹 but since CRA 2.0 I had to rewrite the REGEX to match the new chunks name pattern. I have tried the recommendation which @stereobooster has mentioned but this works only for Webpack >= 4.6

const fs = require("fs");

const pathToEntry = "./build/index.html";
const bundlesRegExp = /\/static\/\w+\/\w+.[a-z0-9]+.[a-z0-9]+.\w{2,3}/g;
const splitBy = "</title>";

const builtHTMLContent = fs.readFileSync(pathToEntry).toString();
const links = builtHTMLContent.match(bundlesRegExp);
const parts = builtHTMLContent.split(splitBy);

let fileWithPreload = [parts[0], splitBy];

links.forEach(link => {
  let fileType = "script";

  if (/\.css$/.test(link)) {
    fileType = "style";
  }
  fileWithPreload = [
    ...fileWithPreload,
    `<link rel="preload" href=".${link}" as="${fileType}">`
  ];
});

fileWithPreload = [...fileWithPreload, parts[1]];

fs.writeFileSync(pathToEntry, fileWithPreload.join(""));

Can I use /* webpackPrefetch: true */ in latest [email protected] ?
It looks like it does not work

It looks like a feature of the HTML webpack plugin that is not yet shipped :-/

It looks like a feature of the HTML webpack plugin that is not yet shipped :-/

aha! many thanks for the answer. where can I track the progress or contribute to this feature?

I'm not sure I read it in the comments of the Medium article that describes the feature :-(

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.

Bad stale bot, bad bot, don't act this way

This issue has been automatically closed because it has not had any recent activity. If you have a question or comment, please open a new issue.

No recent activity? What the fk

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jnachtigall picture jnachtigall  路  3Comments

fson picture fson  路  3Comments

stopachka picture stopachka  路  3Comments

adrice727 picture adrice727  路  3Comments

dualcnhq picture dualcnhq  路  3Comments