Applying Styles to a Next.js Blog
TL;DR
In the previous article, I got markdown rendering working properly, so the next step is applying styles. As mentioned in the first article, the targets are:
- material-ui
- Code syntax highlighting with Prism.js
- Mathematical formulas with amp-mathml
- GitHub markdown CSS
Making these AMP-compatible is the real challenge.
Applying Styles
Prism.js && GitHub Markdown CSS
Download the CSS from the prism.js official site. Download from github-markdown-css. Since github-markdown-css is auto-generated and uses !important, which isn't AMP-compatible, we remove those parts.
Starting with Next.js 12, loading CSS in _document.js now throws an error.
As a workaround, I'm currently saving CSS as JavaScript strings, importing them in components, and expressing them via styled-jsx.
css.js
Create a file like the above, then:
component.jsx
Import it in the component and pass it directly to styled-jsx. This approach works for now.
2021/07/01 revision - Next.js v11
When using webpack5, you can implement raw-loader functionality using asset modules. First, write the configuration in next.config.js. Since we're doing full AMP, we don't expect CSS to be imported normally.
next.config.js
This allows you to load CSS files like raw-loader would.
_document.js
2020/9/7 Implementation using raw-loader
After that, use raw-loader to import CSS in _app.tsx and embed it directly. Ideally, I'd want to load it only on Markdown pages, but...
It has some material-ui elements mixed in, but _document.js looks like this:
_document.js
By using refractor in the custom loader to tokenize code, syntax highlighting works even with AMP. Due to ordering issues, the dark theme for prismjs didn't render properly, so I set the background to black in github-markdown-css instead.
example
amp-mathml
KaTeX is not AMP-compatible.
Instead, first use remark-math to convert formulas into math and inlineMath nodes. Then use the custom loader to embed amp-mathml for type === "math" and type === "inlineMath". Note that inline formulas are children of paragraph nodes.
example
Inline formula
A regular formula:
material-ui
2021/09/23
The Material-UI version has been updated, requiring various configuration changes. Since it's Emotion-based, you need to be careful when making it AMP-compatible. Apparently, using extractCriticalToChunks from @emotion/server is important (reference).
The reason is that when server-side rendering with next.js, CSS loading can sometimes get reset (reference). This actually happened on my site and cost me quite a bit of time.
Fortunately, the material-ui team provides official template examples (JavaScript, TypeScript). Using these as reference, update _app.tsx and _document.tsx. Also, there can be className issues between Next.js links and Material-UI links, so create a Link component.
Note that components using !important cannot be used.
Impressions
That's how style application went. However, material-ui requires quite a bit of CSS-like work, which is pretty challenging. Bootstrap handled most things automatically, so my CSS skills are truly lacking.