About this blog

This Next.js website is a personal project to showcase effective typography & theming, but also a playground to test the new Material Dynamic color scheme. Post contents were auto-generated and are just meant as an example (LLM opinions not my own!).

Color Scheme

The color picker FAB uses the great material-color-utilities library behind the scenes to create the CSS color scheme. The Material guide constantly references the Figma M3 Design Kit to build a color scheme, but I've found it a bit clunky to use: the plugin takes a long time to update all design files every time you switch the source color, and it doesn't have an option to choose between all the different scheme types.

By using the library directly, I'm able to choose my preferred scheme type (SchemeFidelity on this blog) and dynamically update the blog's CSS variables for different color roles (--primary, --onPrimary, --secondary, etc...).

The <ColorPicker> component also saves the user's color preference to localStorage. To avoid writing to storage too often, I've implemented a debouncedSaveColor() method that makes sure the component only writes to it at most once per second.

Rendering

To fully take advantage of Next.js' capabilities, I've chosen Static Site Generation (SSG) to pre-render all possible blog routes at build time. This means that all dynamic routes like posts/[year]/[slug] implement the generateStaticParams() function, which runs during the build step to generate all possible parameter combinations and their associated HTML page. This is appropriate for a blog since the content is mostly static: blog markdown files are not updated that often.

One drawback of this approach is that user preferences (such as their favorite color) are not known at build time, which creates a FOUC (in our case, Flash Of Unwanted Color when the user loads the blog). A naive fix to this problem would be to implement a loading screen while the saved color is fetched on the client-side, but this negates the faster First Paint that SSG gives us. Josh Comeau has a very good explanation of different rendering strategies on his primer on Server Side Rendering.

Another solution would be to use cookies, but this would make the framework automatically switch to Dynamic Rendering (instead of our preferred Static Rendering). The real issue, of course, is that the server doesn't have access to user preferences saved client-side.

The solution I chose was to directly inject a <script> tag in the HTML head of the global layout. The script does need access to the Material SchemeFidelity class, so I had to bundle it into a minified js file with webpack, but the resulting bundle is not very big at 54 kB (16kB transferred), and it is cached by the browser. The script also only needs to run once, when the first blog page is loaded. The result is great: FOUC is completely gone!

Typography

The blog uses the AR One Sans variable font for both headings and body type. Paragraphs have an increased line-height and a max-width: 65ch that makes it easy to scan text at a glance. Conversely, headings have a tighter letter-spacing to make them more legible. Compare the default & tightened spacing :

Default Spacing

Tighter Spacing

I've created a simple system to automatically generate type scales for typography tokens. It uses a default $scale-ratio: 1.125 (Major second type scale, as recommended by Material) with a body font size of 16px to create a cohesive set of font sizes:

@mixin display-large {
  font-size: getFontSize($body-font-size, $scale-ratio, 12);
}
// @mixin display-medium {...}
// @mixin display-small {...}

@function getFontSize($root-size, $scale-ratio, $scale-position) {
  @return round(calc($root-size * pow($scale-ratio, $scale-position)), 1px);
}

This makes it very easy to adjust typography when switching fonts, by only having to modify a handful of variables.