Skip to content

Commit 114fc03

Browse files
committed
fix(header): 右上角日期客户端实时刷新,避免 SSG 冻结 build 时刻
Header 右侧日期原先在 server component 用 new Date() 渲染,SSG/ISR 下会被冻结在 build 时刻,导致线上长期显示陈旧日期。改为客户端组件 LiveDate,沿用 LiveEditionLabel 的固定悉尼时区 + 每秒刷新模式, 首帧用 server 时间戳保证 hydration 一致。
1 parent 04ec76e commit 114fc03

2 files changed

Lines changed: 43 additions & 11 deletions

File tree

app/components/Header.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,18 @@ import { Github as GithubIcon } from "./icons/Github";
88
import { AuthNav } from "./AuthNav";
99
import { BrandMark } from "./BrandMark";
1010
import { LiveEditionLabel } from "./LiveEditionLabel";
11+
import { LiveDate } from "./LiveDate";
1112

1213
export async function Header() {
1314
const t = await getTranslations("header");
14-
const now = new Date();
15-
const editionTimestampMs = now.getTime();
16-
const formattedDate = now.toLocaleDateString("en-US", {
17-
month: "long",
18-
day: "numeric",
19-
year: "numeric",
20-
timeZone: "Australia/Sydney",
21-
});
15+
const editionTimestampMs = Date.now();
2216
return (
2317
<header className="fixed top-0 w-full z-50 bg-[var(--background)] border-b border-[var(--foreground)] py-2 transition-colors duration-300">
2418
<div className="container mx-auto px-6">
2519
<div className="flex items-center justify-between border-b border-[var(--foreground)] pb-2 mb-2 transition-colors duration-300">
2620
<LiveEditionLabel initialTimestamp={editionTimestampMs} />
2721
<BrandMark priority />
28-
<div className="font-mono text-[10px] uppercase tracking-widest text-neutral-500">
29-
{formattedDate}
30-
</div>
22+
<LiveDate initialTimestamp={editionTimestampMs} />
3123
</div>
3224

3325
<div className="flex items-center justify-end md:justify-between h-10">

app/components/LiveDate.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
5+
type LiveDateProps = {
6+
// milliseconds since epoch
7+
initialTimestamp: number;
8+
};
9+
10+
// 固定时区(悉尼)保证服务端/客户端首帧一致,避免 hydration mismatch;
11+
// 挂载后每秒刷新,避免 SSG/ISR 把 build 时刻的日期冻住。
12+
const TIMEZONE = "Australia/Sydney";
13+
14+
function formatDate(ms: number) {
15+
return new Date(ms).toLocaleDateString("en-US", {
16+
month: "long",
17+
day: "numeric",
18+
year: "numeric",
19+
timeZone: TIMEZONE,
20+
});
21+
}
22+
23+
export function LiveDate({ initialTimestamp }: LiveDateProps) {
24+
const [formattedDate, setFormattedDate] = useState(() =>
25+
formatDate(initialTimestamp),
26+
);
27+
28+
useEffect(() => {
29+
const update = () => setFormattedDate(formatDate(Date.now()));
30+
update();
31+
const intervalId = setInterval(update, 1000);
32+
return () => clearInterval(intervalId);
33+
}, []);
34+
35+
return (
36+
<div className="font-mono text-[10px] uppercase tracking-widest text-neutral-500">
37+
{formattedDate}
38+
</div>
39+
);
40+
}

0 commit comments

Comments
 (0)