TL;DR
If the web app's UI depends on the user's local time, make sure that static site generators such as Next.js will produce HTML pages only on the client-side.
For example, if a React app's UI turns into the dark mode automatically after 6pm at the user's local time, use the useEffect()
hook to generate HTML pages. Otherwise, as shown in the image above, buttons (statically generated) may get rendered in the dark mode while the embedded Google Maps (rendered at the client-side) is shown in the light mode, or vice versa.
Introducing the context
I'm making a web app called My Ideal Map App, which embeds Google Maps full-screen, to improve the user experiences of Google Maps. (For detail, see the following article of mine.)
Day 1: Creating SaaS to solve my own problem
Masa Kudamatsu ・ Aug 16 '21
One improvement is to automatically turn on the dark mode after 6pm in the user's local time. The app is meant to be used on a mobile device when the user is going out. I want its UI to be bright at daytime and dark at nighttime, without the need to manually switch the dark mode on and off. (For detail, see the following article of mine.)
Day 5: Switching embedded Google Maps into custom dark mode after 6pm
Masa Kudamatsu ・ Sep 8 '21
I've also designed the dark mode color scheme for buttons to be shown on top of the embedded Google Maps:
Day 9: Picking the dark-mode color palette for web app buttons logically
Masa Kudamatsu ・ Oct 15 '21
After coding the rendering of buttons so they will be shown in the dark mode after 6pm along with the embedded Google Maps, I deployed the app to Cloudflare Pages. Cloudflare Pages assigns the unique URL to each deployment so I can check how the browser shows the production version of the web app I'm making.
Bug
It was around 3pm in Japan (where I live). So visiting the URL should show the light mode version of My Ideal Map App, as in the left half of the following image:
My Ideal Map App's light mode UI (left) and dark mode UI (right) (screenshot by the author)
But I saw this:
Dark gray cloud buttons over the map in light mode (screenshot by the author)
The map is rendered in the light mode as expected. But buttons are in the dark mode, looking like rainy clouds... :-)
You can see the deployment with this bug at https://05da7f84.mima.pages.dev. If your local time is between 6am and 6pm, you will see these rainy clouds. :-)
Incidentally, for why I've made buttons look like clouds, see the following article of mine:
Day 7: Making buttons look like "clouds" for embedded Google Maps
Masa Kudamatsu ・ Sep 30 '21
How can it be possible to make the dark mode co-exist with the light mode? I really scratched my head.
Root cause: Pre-rendering
It turns out that the reason is the pre-rendering of buttons with Next.js, a React framework that I've been using to build My Ideal Map App.
Next.js pre-renders all the pages by default (see Next.js Documentation). That is, when a web developer uploads the code, the server will immediately run JavaScript to generate HTML pages. When the user logs on to the web app, these pre-built HTML pages will be sent to the browser.
(If you're unsure of what benefits "pre-rendering" brings about, have a look at the article that I wrote after having the same question more than a year ago: Kudamatsu 2020).
What happened to me was as follows: I deployed the web app to the Cloudflare Pages server when the server's clock was after 6pm (Cloudflare has many servers across the world). Which made the server build the buttons in the dark mode.
However, the embedded Google Maps is always rendered by the browser (this fact is not clearly written anywhere in Google Maps Platform documentation, but it's pointed out by many such as Starkov 2017). So when I visited the deployed web app around 3pm in my local time, the map got rendered in the light mode.
As a result, I saw the buttons in the dark mode rendered over the map in the light mode...
The problem is not specific to the deployment to the server at a different time zone or to the client-side rendering of embedded Google Maps. Since the time at which the web app is deployed always differs from the time at which the user accesses the app, the user may see a UI that is not as intended.
A lesson is learned. If a web app's UI is dependent on the user's local time, be careful of using static site generators such as Next.js and Gatsby.
A work around: useEffect()
So I need to find a way to render buttons not by the server at the time of deployment but by the browser at the time of users accessing the site (so called “client-side rendering”).
Maybe I shouldn't use Next.js but use Create React App instead (which only allows client-side rendering). But Next.js has many other convenient features for web developers, and I'm used to using it for building a web app (I've made Triangulum Color Picker with Next.js). So I don't want to switch to another React framework.
After quite a bit of struggle over half a day, I found a solution suggested by Dong (2020), which is more generally discussed in Comeau (2021).
The trick is to use the useEffect()
hook. I've learned that the code inside the useEffect()
hook will be run only on the browser, not on the server that pre-renders React components.
Here's how it works for my case.
The code generating the bug
In the pages/index.js
(which Next.js will transform into the index.html
page), I initially had the following code (showing only the relevant part):
// pages/index.js
import {NightModeProvider} from '../context/NightModeContext';
import MenuButton from '../components/MenuButton';
import SearchButton from '../components/SearchButton';
import LocatorButton from '../components/LocatorButton';
import SavePlaceButton from '../components/SavePlaceButton';
import Map from '../components/Map';
function HomePage() {
return (
<>
<NightModeProvider>
<MenuButton />
<SearchButton />
<LocatorButton />
<SavePlaceButton />
<Map /> {/* where Google Maps will be embedded */}
</NightModeProvider>
</>
);
}
export default HomePage;
I've coded four buttons and the <Map>
component that embeds Google Maps. To switch on/off the dark mode for each of these five components, I wrap them all with <NightModeProvider>
, which is created out of React Context Provider:
// context/NightModeContext.js
import {createContext} from 'react';
const NightModeContext = createContext();
export function NightModeProvider(props) {
let nightMode;
const currentTime = new Date();
const currentHour = currentTime.getHours();
if (currentHour < 6 || currentHour >= 18) {
nightMode = true;
} else {
nightMode = false;
}
return <NightModeContext.Provider value={nightMode} {...props} />;
}
It checks whether the local time is between 6pm and 6am. If so, it sets nightMode
to be true
and passes it to child components. (See Section 3.2 of Day 5 of this blog series for detail.)
The code for fixing the bug
To prevent the server from pre-rendering button components, I introduce a state variable called clientSideRendering
which is initially set to be false
. Then, render button components only if clientSideRendering
is true
:
import {useState} from 'react'; // ADDED
...
function HomePage() {
const [clientSideRendering, setClientSideRendering] = useState(false); // ADDED
return (
<>
<NightModeProvider>
{/* REVISED FROM HERE */}
{clientSideRendering && <MenuButton />}
{clientSideRendering && <SearchButton />}
{clientSideRendering && <LocatorButton />}
{clientSideRendering && <SavePlaceButton />}
{/* REVISED UNTIL HERE */}
<Map />
</NightModeProvider>
</>
);
}
...
This way, when deployed, the server won't render these button components because clientSideRendering
is false
.
To allow the user's browser to run the code for rendering buttons, turn clientSideRendering
into true
inside the useEffect()
hook:
import {useState, useEffect} from 'react'; // REVISED
...
function HomePage() {
const [clientSideRendering, setClientSideRendering] = useState(false);
// ADDED FROM HERE
useEffect(() => {
setClientSideRendering(true);
}, []);
// ADDED UNTIL HERE
return (
<>
<NightModeProvider>
{clientSideRendering && <MenuButton />}
{clientSideRendering && <SearchButton />}
{clientSideRendering && <LocatorButton />}
{clientSideRendering && <SavePlaceButton />}
<Map />
</NightModeProvider>
</>
);
}
The code inside the useEffect()
hook will be run only after the components listed as a return value have been rendered. Then, as the setClientSideRendering
state variable changes, all the components will be re-rendered based on the new value of the state, which renders all the buttons (based on the user's local time).
This process won't be triggered when the server pre-renders components, which avoids rendering buttons in the light or dark mode based on the time at which the web app is deployed.
A final touch: the useEffect()
hook needs to be run only once after the user visits the page. So the second argument of the useEffect()
hook is set to be an empty array, i.e., []
(see React documentation for detail).
Now I get the following UI after 6pm:
Dark mode buttons on the dark mode embedded Google Maps (screenshot by the author)
Here is a demo hosted by Cloudflare Pages. If you don't see the dark mode UI as in the image above after 6pm in your local time, file a bug report by posting a comment to this article. ;-)
Finally, I'm done with buttons for My Ideal Map App. Next step is to add functionalities to these buttons. I'll start with the locator button (the one with the flight takeoff icon), which will show the user's current location on the map.
References
Comeau, Josh (2021) “The Perils of Rehydration”, joshwcomeau.com, May 30, 2021.
Dong, Hao (2020) “Render client-side only component in Next.js”, Hao's Learning Log, Jun 30, 2020.
Kudamatsu, Masa (2020) “Beyond create-react-app: Why you might want to use Next.js or Gatsby instead”, Web Dev Survey from Kyoto, Nov 30, 2020.
Starkov, Ivan (2017) “It is expected, google map api, over which this component build does not support server tile rendering...”, GitHub Issues for google-map-react, #302, Feb 15, 2017.
Top comments (0)