-
I want to get a better understanding of the execution environments in Remix so as to ward off timezone related issues that I may encounter in the future. Basically, when I call I'll put some sample code below to explain what I mean better. import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { useEffect, useState } from "react";
export function loader() {
const loaderDate = new Date(); // <- Run on the server only?
return json({ loaderDate: formatDate(loaderDate) });
}
export default function IndexPage() {
const { loaderDate } = useLoaderData<typeof loader>();
const date = new Date(); // <- Is this run on the server or the client
// Or both, depending on how we got here?
const [dateState, setDateState] = useState(new Date());
useEffect(() => {
const dateEffect = new Date(); // <- Is this run on the client only?
setDateState(dateEffect);
}, []);
return (
<div>
<p>loaderDate: {loaderDate}</p>
<p>date: {formatDate(date)}</p>
<p>dateState: {formatDate(dateState)}</p>
</div>
);
}
function formatDate(date: Date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are zero-based
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
const offset = date.getTimezoneOffset();
const offsetHours = Math.abs(Math.floor(offset / 60));
const offsetMinutes = Math.abs(offset % 60);
const offsetSign = offset < 0 ? "+" : "-";
const offsetString = `${offsetSign}${String(offsetHours).padStart(
2,
"0",
)}:${String(offsetMinutes).padStart(2, "0")}`;
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${offsetString}`;
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
I had a fruitful discussion regarding this issue in the Remix Discord chat. I'll be writing down what I learned here for others. First off, in the The However if we need to support a requirement that has to factor in the user's current timezone, like for example if we need the current date in their time zone, things get a little tricky. E.g. imagine if you wanted to create a site to check if it is the anniversary of some event, and if it is show a message. Simply using the date part of the UTC timestamp may result in incorrectness depending on the user's timezone. The To solve this, we can generate the local date on the server by picking up the user's time zone and using an appropriate datetime library like dayjs. Picking up the user's time zone can be implemented by a user preference cookie or client hints. There are other solutions, such as using CSS to hide the date before it is ready. |
Beta Was this translation helpful? Give feedback.
I had a fruitful discussion regarding this issue in the Remix Discord chat. I'll be writing down what I learned here for others.
First off, in the
const date = new Date()
line directly in the component, the server's timezone is used first on the initial server render, and then the client's timezone is used on subsequent client renders. Because of this mismatch, a hydration error will likely occur if the server and client have different time zones.The
loaderDate
is only generated with the server's timezone as expected. If a stable, timezone specific timestamp is required, the loader and action are probably the best places to generate it.However if we need to support a requirement that ha…