Leveraging dynamic imports to make Lighthouse happier

November 1, 2020
4 min read
I'm building this website with Next.js and charmed by the delightful developer experience we get when it's combined with Vercel.
In addition to eliminating the friction, Vercel also provides analytics and Lighthouse integration to monitor how my website is performing when it comes to vital metrics for the user experience.
And that's precisely what I did right after deploying a bearable version. But the results were not satisfying at all.
Lighthouse score before the optimization
My minimal home page with three lines of text and a handful of links made Lighthouse complain sorely about the performance.
The report is clear, it tells that I'm basically making your computer load and parse a bunch of irrelevant JavaScript.
Lighthouse score details, before the optimization
I thought that's an ethos to have if you are building a publishing thingy nowadays, or am I in the wrong Medium? ๐Ÿ‘น๐Ÿ‘น๐Ÿ‘น

Finding out what I am excessively loading

The report shows which chunk is not utilized. Unfortunately, the code is minified.
I have two choices:
  • Be a responsible, clean coder and inspect the source maps by using @next/bundle-analyzer to find out what's wrong.
  • Remember that's a fun project, and double the amount of fun with puzzles by guesstimating what it could be.
Proceeding with the latter, I know one thing which doesn't get minified: error messages!
Digging up the excessively loaded code
And searching for
only one of 'allowedTypes' and 'disallowedTypes' should be defined
in the
is a good next step for approaching to the crux.
Search results for the error message.
Then I realize it's seemingly related to markdown. Now that the scope is narrowed down, it's time to discover what I'm doing wrong.

How does this website render markdown

I tend to rely on markdown for rendering any kind of content, and using react-markdown to render raw content to HTML. It has a nice API that allows you to render specific elements by using custom renderers.
I copy/pasted the snippet in docs to render code blocks with
It looked like this before the refactor:
// Markdown.jsx
import ReactMarkdown from 'react-markdown'
import { Prism } from 'react-syntax-highlighter'

const Code = (props) => <Prism language={props.language}>{props.value}</Prism>

const Markdown = ({ children }) => (
      code: Code,

export default Markdown
That might be a heavy library to load if I'm not planning to render any code blocks since it contains all the parsing logic with themes and such.
And as you can guess, the homepage does not contain any code snippets, which is why Lighthouse is unhappy: the eager load.

Dynamically loading modules with Next.js

allows lazy loading with the suspense API, it's not available for
since the
support has not implemented yet.
Luckily, our thoughtful friend
offers an alternative API for dynamic imports. It's called next/dynamic and pretty straightforward for such a use-case like this.
First, I changed my usage of
to load and register parsers only for the languages I use.
// MarkdownCodeBlock.jsx
import { PrismLight } from 'react-syntax-highlighter'
import jsx from 'react-syntax-highlighter/dist/cjs/languages/prism/jsx'
import tsx from 'react-syntax-highlighter/dist/cjs/languages/prism/tsx'

PrismLight.registerLanguage('jsx', jsx)
PrismLight.registerLanguage('tsx', tsx)

const MarkdownCodeBlock = ({ language, value }) => (
  <PrismLight language={language}>{value}</PrismLight>

export default MarkdownCodeBlock
I also changed the strategy of using syntax highlighter to be on demand, so the other pages using markdown (such as book notes) won't load
and friends.
// Markdown.jsx
import ReactMarkdown from 'react-markdown'
import dynamic from 'next/dynamic'

const MarkdownCodeBlock = dynamic(() => import('./MarkdownCodeBlock'))

const Markdown = ({ children }) => (
      code: MarkdownCodeBlock,

export default Markdown

...and then, did they live happily after? ๐Ÿค–๐Ÿ‘จโ€๐Ÿ’ป

In the end, the performance score of the home page increased dramatically.
Lighthouse score, after the optimization
This approach doesn't eradicate the CPU tax of loading and executing the JS on the pages that I need to render a code editor, such as this post.
Nonetheless, it divides the amount of work while browsing and progressively loads the required code, which is more helpful than not doing it. ๐Ÿคจ