I build my app with yarn build
.
It generate a 3.27MB build\static\js\main.8dc5bf7f.chunk.js
file:
3.27 MB build\static\js\main.8dc5bf7f.chunk.js
82.79 KB build\static\js\2.61d04f1f.chunk.js
2 KB build\static\css\main.275d97bd.chunk.css
1.93 KB build\static\css\2.8380becc.chunk.css
768 B build\static\js\runtime~main.848c2454.js
The bundle size is significantly larger than recommended.
Consider reducing it with code splitting
You can also analyze the project dependencies
I used source-map-explorer
to analyze the file and it show:
So the file include all of the images of my entire app.
But funny thing is the build still have a static/media
directory which has all of those images in .png form. And apparently the app still load those png files to show the images.
So what are those things in the js file? I wonder if they're even used at all?
Please:
This is a tough problem that most engineers have as the app grows larger.
In your case you're importing a lot of small PNGs that get base64 encoded due to CRA's webpack setting. You can override that behavior with a library like react-rewired. While it's a bit more involving process, I'd recommend that over maintaining an external image repository. It's easier to test and use in a development environment. Especially when offline.
I also see that most of your views are bundled together in main.js.
The secret to cutting down the bundle size is in leveraging code splitting and lazy loading.
Here's the link to the video that I put together for this: https://www.youtube.com/watch?v=j8NJc60H294
In a nutshell, I suggest the following patterns:
This is commonly achieved by using React.lazy()
import React, { Suspense, lazy } from 'react';
// import MyRoute from 'routes/MyRoute' - we are replacing this import
const MyRoute = lazy(() => import('routes/MyRoute'));
const Loading = () => <div>Loading route...</div>;
const View = () => (
<Suspense fallback={Loading}>
<MyRoute />
</Suspense>
);
Every page has Critical Path content. It's the content your visitors want to experience first. It's the primary purpose of a view. It's usually in the above the fold area (the part of the view they see when the page loads, without any scrolling).
However, that part can also be dissected based on priority. A Hero is a good critical path example, so we should prioritize it. A Hamburger menu is a great element to de-prioritize because it requires interaction to be viewed.
Anything below the fold can be de-prioritized too. A great example of this is Youtube comments. They only get loaded if we scroll down sufficiently.
You can follow the same principle as above to prioritize critical path content:
import React, { Suspense, lazy } from 'react';
import Hero from 'component/Hero'
const Burger = lazy(() => import('components/Burger'));
// The fallback should be used to show content placeholders.
// It doesn't have to be a loading indicator.
const Loading = () => <img src="path/to/burger/icon" alt="Menu"/>;
const View = () => (
<main>
<Hero />
<Suspense fallback={Loading}>
<Burger />
</Suspense>
</main>
);
Following the principles above, I suggest creating simple abstract components for every library. Let's say you need animations and you use Framer Motion. You can import it in one place and use everywhere.
Many libraries ship with named exports and lazy()
doesn't support that. I wrote a very simple library react-lazy-named that helps with that.
// AnimatedDiv.js
import React, { lazy } from 'react';
// This is similar to
// import { motion } from 'framer-motion';
// const Div = motion.div;
// Also, we hint webpack to use resource preloading (think service worker goodness)
const Div = lazy(() => import('framer-motion' /* webpackPreload: true */), 'motion.div'));
// Use regular div as a fallback.
// It will be replaced with framer-motion animated Div when lazy-loaded
const AnimatedDiv = (props) => (
<Suspense fallback={<div>{props.children}</div>}>
<Div {...props} />
</Suspense>
);
If you have any questions or need and help with this you can reply here or in the comments of the aforementioned video on Loading React apps in under 3 seconds
Basically, anything you import
in your app and handle bundling it with webpack would be considered as a dependency.
so: import img from path/to/img
would make it a dependency, thus, included in your bundle, that's what you want to escape.
There are two possible scenarios to work-around this:
For me, I would go with number 1.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With