Linen is an open source Google searchable open-source Slack/Discord alternative for communities. Our vision is to merge the advantages of a forum with the real-time interaction of chat. We launched the first version of Linen a few months ago. Our initial client bundle size of ~1MB gzipped was already more streamlined than most chat apps. But we knew we could do better, so after a bunch of small optimizations, we shrunk it down to ~2MB parsed and ~500KB gzipped. We wanted to share the techniques we used and the process we went through.
Why Focus on Reducing Bundle Size?
Linen has millions of pages, and search engines have a specific budget on how much compute resources they allocate to crawling a website. By reducing the bundle size by half, we can potentially double the number of pages that search engines can crawl with all things being equal. Beyond search engine friendliness, a smaller bundle size also means faster initial page load speed, especially on slower connections. As a bonus side effect, it makes us more disciplined when choosing packages and libraries, which helps reduce the security attack surface area.
What does these numbers mean:
To identify areas for improvement, we used the webpack bundle analyzer to target our app/web directory, allowing us to visually pinpoint the space-consuming components.
This returned three numbers.
Stat: 7.27 MB
- Stat is what webpack returns the raw amount of packages and files that is in your folder this is the least important of the three since it isn’t what the client ultimately sees.
- Parsed is what gets ran on the client side after the bundler minimizes/uglifies your package this is how much space it takes up on the client’s machine. We want to reduce this number but not the highest priority.
- Gzipped is what gets sent over the internet and downloaded this will impact download and page load speed the most. For us we cared the most about gzipped size since it will impact bandwidth the most.
For a more detailed look we’ve made our January bundle size public here
Our Optimization Strategies:
Fixing import issues and tree shaking problems
We saw that React Icon utilized ~200KB parsed and ~50KB gzipped, even though we were only using a few SVGs from the library. Most of the time if you are only selectively importing specific functions your bundler will remove dead code through tree-shaking. We found that react-icons had an issue that lead to everything being imported. This meant that we were including every single react-icon in our package whether we need it or not. The work around was to use change the ipmorts to react-icons/all-files instead of react-icons, reducing the bundle size to 10KB parsed and 2KB gzipped.
Replace libraries with custom code
HeadlessUI didn’t support individual importing. Although the package is small we were only using one component. We decided to replace the library with own custom written component
We also noticed that we were only using AWS client for s3 upload on the client side and it was taking up significantly more bundle size we need so we replaced the entire client side package with a 2 api calls to the AWS api.
Finally we were using _loadash’s Union method. That was a bit heavier function which handled a lot more edge cases we needed so we replaced it all with our own custom function.
All in all this strategic move saved us ~600KB parsed and ~100KB gzipped. It can be a bit time consuming but it helps you become more disciplined about which packages to add
Moving code to the backend
Finally, we saw that highlight.js (the library for code highlighting) was taking up 30% of our bundle size (~949.06KB parsed, ~292.58KB gzipped). Since most of our users are technical communities, it was obviously required. There was no way we could write our own, and it didn’t seem like there was a lighter alternative since the ability to parse programming languages is very complicated. We ended up moving the code highlight code to a backend api that would cache the results. This meant that our bundles wouldn’t need to load entire highlightjs.
Ultimately we were able to drop our bundle size down by 50% which meant faster load time and better user experience and SEO performance. We focused mainly on the low hanging fruits which ended up taking us a few days of engineering effort. All in all we were happy with the investment we made and took less time than we expected.
This is what our bundle size looked like at the end (April of 2023)
If you see something that you think we can reduce feel free to open a Github issue or pull request:
in our repo