fix: render local-timezone dates client-side only
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 14s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 37s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m10s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m28s

- started-tournament header and tournament list formatted times in the
  server timezone during ssr, mismatching hydration on every load
This commit is contained in:
2026-08-27 14:14:03 -07:00
parent 7bda138d69
commit 6bb3ee4bc7
6 changed files with 34 additions and 25 deletions
@@ -2,11 +2,15 @@ import { Group, Stack, ThemeIcon, Text } from "@mantine/core";
import { Tournament } from "../../types";
import { CalendarIcon, MapPinIcon, TrophyIcon } from "@phosphor-icons/react";
import { useMemo } from "react";
import { useMounted } from "@mantine/hooks";
import { Trans, useLingui } from "@lingui/react/macro";
import GlitchAvatar from "@/components/glitch-avatar";
const Header = ({ tournament }: { tournament: Tournament }) => {
const { i18n } = useLingui();
// Local-timezone text cannot be server-rendered: the pod formats in UTC and
// hydration mismatches on every load.
const mounted = useMounted();
const tournamentStart = useMemo(
() => new Date(tournament.start_time),
[tournament.start_time]
@@ -48,6 +52,7 @@ const Header = ({ tournament }: { tournament: Tournament }) => {
<CalendarIcon size={14} />
</ThemeIcon>
<Text size="sm" c="dimmed" lineClamp={1}>
{mounted && (
<Trans>
{tournamentStart.toLocaleDateString(i18n.locale, {
weekday: "short",
@@ -60,6 +65,7 @@ const Header = ({ tournament }: { tournament: Tournament }) => {
minute: "2-digit",
})}
</Trans>
)}
</Text>
</Group>
</Stack>
@@ -5,6 +5,7 @@ import { TournamentInfo } from "../types";
import { Fragment, useCallback } from "react";
import React from "react";
import { TrophyIcon, CalendarIcon, MapPinIcon } from "@phosphor-icons/react";
import { useMounted } from "@mantine/hooks";
import { Trans, useLingui } from "@lingui/react/macro";
interface TournamentListProps {
@@ -18,6 +19,7 @@ interface TournamentListItemProps {
const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps) => {
const { i18n } = useLingui();
const mounted = useMounted();
const startDate = tournament.start_time ? new Date(tournament.start_time) : null;
return (
@@ -43,7 +45,8 @@ const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps)
<CalendarIcon size={10} />
</ThemeIcon>
<Text size="xs" c="dimmed">
{startDate.toLocaleDateString(i18n.locale, {
{mounted &&
startDate.toLocaleDateString(i18n.locale, {
month: 'short',
day: 'numeric',
year: 'numeric'
+2 -2
View File
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {Spieler angemeldet} other {Spieler angemeldet}}"
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51
#: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}"
msgstr "{0} um {1}"
@@ -1843,7 +1843,7 @@ msgstr "Noch keine Teams angemeldet"
msgid "No teams found matching \"{search}\""
msgstr "Keine Teams gefunden, die zu \"{search}\" passen"
#: src/features/tournaments/components/tournament-list.tsx:95
#: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found"
msgstr "Keine Turniere gefunden"
+2 -2
View File
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {player enrolled} other {players enrolled}}"
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51
#: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}"
msgstr "{0} at {1}"
@@ -1843,7 +1843,7 @@ msgstr "No teams enrolled yet"
msgid "No teams found matching \"{search}\""
msgstr "No teams found matching \"{search}\""
#: src/features/tournaments/components/tournament-list.tsx:95
#: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found"
msgstr "No tournaments found"
+2 -2
View File
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {jugador inscrito} other {jugadores inscritos}}"
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51
#: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}"
msgstr "{0} a las {1}"
@@ -1843,7 +1843,7 @@ msgstr "Aún no hay equipos inscritos"
msgid "No teams found matching \"{search}\""
msgstr "No se encontraron equipos que coincidan con \"{search}\""
#: src/features/tournaments/components/tournament-list.tsx:95
#: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found"
msgstr "No se encontraron torneos"
+2 -2
View File
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {人のプレイヤーが登録済み} other {人のプ
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51
#: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}"
msgstr "{0} {1}"
@@ -1843,7 +1843,7 @@ msgstr "まだチームが登録されていません"
msgid "No teams found matching \"{search}\""
msgstr "「{search}」に一致するチームが見つかりません"
#: src/features/tournaments/components/tournament-list.tsx:95
#: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found"
msgstr "トーナメントが見つかりません"