惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LangChain Blog
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园_首页
T
Tailwind CSS Blog
P
Proofpoint News Feed
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
V
Vulnerabilities – Threatpost
阮一峰的网络日志
阮一峰的网络日志
C
CERT Recently Published Vulnerability Notes
Attack and Defense Labs
Attack and Defense Labs
S
Schneier on Security
Security Archives - TechRepublic
Security Archives - TechRepublic
L
Lohrmann on Cybersecurity
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
The Register - Security
The Register - Security
N
Netflix TechBlog - Medium
Hugging Face - Blog
Hugging Face - Blog
Project Zero
Project Zero
博客园 - 叶小钗
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
Latest news
Latest news
S
SegmentFault 最新的问题
C
Cyber Attacks, Cyber Crime and Cyber Security
Google Online Security Blog
Google Online Security Blog
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
L
LINUX DO - 最新话题
Google DeepMind News
Google DeepMind News
P
Privacy International News Feed
I
InfoQ
F
Fortinet All Blogs
Vercel News
Vercel News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
T
Tenable Blog
B
Blog RSS Feed

Maxime Heckel's Blog

On Rendering the Sky, Sunsets, and Planets - The Blog of Maxime Heckel Shades of Halftone - The Blog of Maxime Heckel Field Guide to TSL and WebGPU - The Blog of Maxime Heckel On Shaping Light: Real-Time Volumetric Lighting with Post-Processing and Raymarching for the Web - The Blog of Maxime Heckel Speaking at Figma Config 2025 - The Blog of Maxime Heckel Post-Processing Shaders as a Creative Medium - The Blog of Maxime Heckel On Crafting Painterly Shaders - The Blog of Maxime Heckel The Art of Dithering and Retro Shading for the Web - The Blog of Maxime Heckel Moebius-style post-processing and other stylized shaders - The Blog of Maxime Heckel Shining a light on Caustics with Shaders and React Three Fiber - The Blog of Maxime Heckel Real-time dreamy Cloudscapes with Volumetric Raymarching - The Blog of Maxime Heckel Painting with Math: A Gentle Study of Raymarching - The Blog of Maxime Heckel Building a magical AI-powered semantic search from scratch - The Blog of Maxime Heckel Beautiful and mind-bending effects with WebGL Render Targets - The Blog of Maxime Heckel Refraction, dispersion, and other shader light effects - The Blog of Maxime Heckel The magical world of Particles with React Three Fiber and Shaders - The Blog of Maxime Heckel The Study of Shaders with React Three Fiber - The Blog of Maxime Heckel Building a Design System from scratch - The Blog of Maxime Heckel Everything about Framer Motion layout animations - The Blog of Maxime Heckel Building a Vaporwave scene with Three.js - The Blog of Maxime Heckel Cubic Bézier: from math to motion - The Blog of Maxime Heckel First steps with GPT-3 for frontend developers - The Blog of Maxime Heckel Building the perfect GitHub CI workflow for your frontend team - The Blog of Maxime Heckel Migrating to Next.js - The Blog of Maxime Heckel Static Tweets with MDX and Next.js - The Blog of Maxime Heckel Advanced animation patterns with Framer Motion - The Blog of Maxime Heckel Scrollspy demystified - The Blog of Maxime Heckel The Power of Composition with CSS Variables - The Blog of Maxime Heckel My first failed SwiftUI project - The Blog of Maxime Heckel Guide to creating animations that spark joy with Framer Motion - The Blog of Maxime Heckel Using Shortcuts and serverless to build a personal Apple Health API - The Blog of Maxime Heckel SEO mistakes I've made and how I fixed them - The Blog of Maxime Heckel Going native: SwiftUI from the perspective of a React developer - The Blog of Maxime Heckel Build your own preview deployment service - The Blog of Maxime Heckel The little guide to CI/CD for frontend developers - The Blog of Maxime Heckel Immigrating to the US - The Blog of Maxime Heckel The physics behind spring animations - The Blog of Maxime Heckel Generate screenshots of your code with a serverless function - The Blog of Maxime Heckel How to use Framer Motion with Emotion styled-components - The Blog of Maxime Heckel Data Fetching with NextJS: What I learned - The Blog of Maxime Heckel Learning in public - The Blog of Maxime Heckel How to fix NPM link duplicate dependencies issues - The Blog of Maxime Heckel Running scheduled cross-browser end-to-end tests on Github CI - The Blog of Maxime Heckel How I built my first custom ESLint rule - The Blog of Maxime Heckel React Lazy: a take on preloading views - The Blog of Maxime Heckel Automated UI accessibility testing with Cypress - The Blog of Maxime Heckel Building a GraphQL wrapper for the Docker API - The Blog of Maxime Heckel Switching off the lights - Adding dark mode to your React app - The Blog of Maxime Heckel Getting started with Typescript on Gatsby - The Blog of Maxime Heckel Rebuilding Redux with Hooks and Context - The Blog of Maxime Heckel Asynchronous rendering with React - The Blog of Maxime Heckel Using Flow generics to type generic React components - The Blog of Maxime Heckel How to efficiently type your styled-components with Flow - The Blog of Maxime Heckel How I got started with Kubernetes on GKE - The Blog of Maxime Heckel React sub-components Part 3: Whitelisting sub-components with flow - The Blog of Maxime Heckel React sub-components Part 2: Using the new Context API - The Blog of Maxime Heckel React sub-components - The Blog of Maxime Heckel Running Golang tests with Jest - The Blog of Maxime Heckel No title No title
Fixing the dark mode flash issue on server rendered websites - The Blog of Maxime Heckel
Maxime Heckel · 2020-04-16 · via Maxime Heckel's Blog

This blog post is a follow up to Switching off the lights - Adding dark mode to your React app that I wrote a year ago. I finally took the time to fix my implementation which caused a lot of issues on server rendered websites and I wanted to share my solution with you.

An ugly hack

When I first added dark mode on my Gatsby projects, I encountered what you might know as the "Dark mode flashing" issue. The colors of the light mode would show up for a brief moment when refreshing a webpage.

Gif showcasing the dark mode flash issue on this blog.

Why does this issue show up? @JoshWComeau explains the reason behind this issue pretty well on his blog post CSS Variables for React Devs:

"Dark Mode" is surprisingly tricky, especially in a server-rendered context (like with Gatsby or Next.js). The problem is that the HTML is generated long before it reaches the user's device, so there's no way to know which color theme the user prefers.

To avoid this issue back when implementing it for the first time I did what I'd call an "ugly hack". I'd avoid rendering the whole website until the theme to render was known, and in the meantime, I'd just render a simple <div/>:

Code snippet from my first dark mode article featuring the ugly hack to avoid "dark mode flash"

1

if (!themeState.hasThemeLoaded) {

10

const theme = themeState.dark ? theme('dark') : theme('light');

This ugly hack caused me some of the most frustrating problems I've had in a while, one of them even took me several days to figure out:

MaximeHeckel

The core of the issue: I was rendering a &lt;div/&gt; when loading the website and reading the localStorage to set the proper theme (since it's async). This stopped gatsby from going further during the SSR build step and hence not generating the pages (with meta tags) of my blog

(Again thank you @chrisbiscardi for taking the time to help me debug this)

I then brought another solution to this problem: add a display: hidden CSS style to the main wrapper until the theme was loaded as featured in this blog post. It fixed my SEO issues, but I was still not satisfied with this fix.

After reading Josh Comeau's blog post on using CSS variables along with Emotion Styled Components, I decided to leverage these to fix the dark mode flashing issue once and for all (no hack this time!).

Using CSS variables in my themes

Originally I had my theme set to an object looking roughly like the following:

Original version of a theme including light and dark mode colors

The cool thing I've learned recently is that it's possible to convert the hardcoded hex values to use CSS Custom Properties in a theme object that is passed to the Emotion Theme Provider.

The first thing to do add these CSS variables in a Emotion Global component:

Emotion global component with CSS Custom properties

1

import { css, Global } from '@emotion/core';

2

import React from 'react';

4

const GlobalStyles = () => (

8

--theme-colors-gray: #f8f8f9;

9

--theme-colors-black: #161617;

13

--theme-colors-black: #161617;

14

--theme-colors-white: #ffffff;

20

export default GlobalStyles;

Then, replace the hex values in the themes with the corresponding CSS variable names:

Updated version of the theme object using CSS Custom Properties

3

background: var(--theme-colors-gray, #F8F8F9),

4

body: var(--theme-colors-black, #161617),

7

background: var(--theme-colors-black, #161617),

8

body: var(--theme-colors-white, #FFFFFF),

Everything should remain pretty much the same, we've simply moved some hex values around and put them in CSS variables under their respective CSS class mode theme-light and theme-dark. Now let's see how this can be leveraged with some good old inline Javascript in a HTML script tag.

Injecting a script

Server rendered websites like Gatbsy let us customize the html.js file. This gives us the possibility to inject a script that will set the proper theme based on the value present in local storage.

If not already available in the src folder the html.js can be copied from the .cache folder of your Gatsby project:

1

cp .cache/default-html.js src/html.js

The following will have to be added to this file:

Javascript script that reads the local storage item with the key 'mode' to load the proper theme

3

var mode = localStorage.getItem('mode');

5

window.matchMedia('(prefers-color-scheme: dark)').matches === true;

6

if (!mode && supportDarkMode) document.body.classList.add('theme-dark');

8

document.body.classList.add('theme-' + mode);

This script does the following:

  1. It looks for a local storage item with a key named mode

  2. It looks for the prefers-color-scheme CSS media query, here we look whether its set to dark, which translates to the user loading the website having a system using dark mode.

  3. If there's no mode set in local storage but the user's system uses dark mode, we add a class theme-dark do the body of the main document.

  4. If there's simply no mode set in local storage we don't do anything, which will end up loading the default theme of our UI

  5. Otherwise, we add the class associated with the mode set in local storage to the body of the document

We can add the script to the html.js file inside the <body> tag as follows:

html.js file featuring our custom script

2

<body {...props.bodyAttributes}>

3

<script key="maximeheckel-theme" dangerouslySetInnerHTML={{ __html:

4

`(function() { try { var mode = localStorage.getItem('mode'); var

5

supportDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches

6

=== true; if (!mode && supportDarkMode)

7

document.body.classList.add('theme-dark'); if (!mode) return;

8

document.body.classList.add('theme-' + mode); } catch (e) {} })();`, }} />

9

{props.preBodyComponents}

13

dangerouslySetInnerHTML="{{"

18

{props.postBodyComponents}

Updating the toggle function

There's one last update to be done: updating the toggle light/dark mode function. We need to add a few lines of code to make sure we add or remove the appropriate CSS class from the body tag, otherwise the colors of our themes will be a bit messed up 😅.

In the example featured in the first blog post this is what the function looked like:

Original function to toggle between light and dark mode

2

const dark = !themeState.dark;

3

localStorage.setItem('dark', JSON.stringify(dark));

4

setThemeState({ ...themeState, dark });

And this is what we need to add to make it work properly again:

Updated function to toggle between light and dark mode

2

const dark = !themeState.dark;

4

document.body.classList.remove('theme-light');

5

document.body.classList.add('theme-dark');

7

document.body.classList.remove('theme-dark');

8

document.body.classList.add('theme-light');

10

localStorage.setItem('dark', JSON.stringify(dark));

11

setThemeState({ ...themeState, dark });

Result

By adding the code featured in the previous parts, we allow the Javascript related to getting the proper theme to be executed before we start rendering the React code. The appropriate class name to the body tag is going to be set immediately which will allow out CSS variables to be set to the proper variables. Then, for the brief moment when our "flash" issue previously occurred, the theme being used does not matter, as the colors are solely based on the CSS variables 🎉! This is what makes the flash disappear under the hood.