diff --git a/frontend/index.html b/frontend/index.html
index eefd45b..0a9860d 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -38,6 +38,7 @@
} catch {}
window.__TSS_BOOT_PREFERENCES__ = { analyticsPreferences, theme }
+ window.__TSS_BOOT_DATA__ = __TSS_BOOT_DATA__
document.documentElement.dataset.theme = theme
document.documentElement.style.colorScheme = theme
document.querySelector('meta[name="theme-color"]')?.setAttribute(
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 68e337a..aabfb49 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1074,11 +1074,28 @@ function GatedAppContent() {
}
function AppContent() {
+ const bootData = window.__TSS_BOOT_DATA__ || {}
const [route, setRoute] = useState(() => parseRoute())
- const [leaderboard, setLeaderboard] = useState({ status: 'idle', data: null, error: null })
- const [playerLeaderboard, setPlayerLeaderboard] = useState({ status: 'idle', data: null, error: null })
- const [homeTeams, setHomeTeams] = useState({ status: 'idle', data: null, error: null })
- const [live, setLive] = useState({ status: 'idle', data: null, error: null, updatedAt: 0 })
+ const [leaderboard, setLeaderboard] = useState(() => (
+ bootData.leaderboard
+ ? { status: 'ready', data: bootData.leaderboard, error: null }
+ : { status: 'idle', data: null, error: null }
+ ))
+ const [playerLeaderboard, setPlayerLeaderboard] = useState(() => (
+ bootData.playerLeaderboard
+ ? { status: 'ready', data: bootData.playerLeaderboard, error: null }
+ : { status: 'idle', data: null, error: null }
+ ))
+ const [homeTeams, setHomeTeams] = useState(() => (
+ bootData.homeTeams
+ ? { status: 'ready', data: bootData.homeTeams, error: null }
+ : { status: 'idle', data: null, error: null }
+ ))
+ const [live, setLive] = useState(() => (
+ bootData.live
+ ? { status: 'ready', data: bootData.live, error: null, updatedAt: Date.now() }
+ : { status: 'idle', data: null, error: null, updatedAt: 0 }
+ ))
const [uptime, setUptime] = useState({ status: 'idle', checks: [], history: [], updatedAt: null })
const [viewers, setViewers] = useState({ status: 'idle', data: null, error: null, updatedAt: null })
const [analyticsPreferences, setAnalyticsPreferences] = useState(() => storedAnalyticsPreferences())
diff --git a/server.cjs b/server.cjs
index 69b75f5..46a7205 100644
--- a/server.cjs
+++ b/server.cjs
@@ -2281,6 +2281,43 @@ function routeStructuredData(origin, seo, canonicalUrl) {
])
}
+function readPublicDataSnapshot(relativePath) {
+ try {
+ const filePath = path.resolve(PUBLIC_DATA_CACHE_DIR, relativePath)
+ const relativeToCache = path.relative(PUBLIC_DATA_CACHE_DIR, filePath)
+ if (relativeToCache.startsWith('..') || path.isAbsolute(relativeToCache)) return null
+ const stat = fs.statSync(filePath)
+ if (!stat.isFile() || Date.now() - stat.mtimeMs > PUBLIC_DATA_CACHE_STALE_MS) return null
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
+ } catch {
+ return null
+ }
+}
+
+function routeBootData(pathname) {
+ const boot = {}
+
+ if (pathname === '/') {
+ const homeTeams = readPublicDataSnapshot('home-teams.json')
+ const live = readPublicDataSnapshot('recent-games.json')
+ if (homeTeams) boot.homeTeams = homeTeams
+ if (live) boot.live = live
+ } else if (pathname === '/teams') {
+ const leaderboard = readPublicDataSnapshot('leaderboard-teams.json')
+ if (leaderboard) boot.leaderboard = leaderboard
+ } else if (pathname === '/players') {
+ const playerLeaderboard = readPublicDataSnapshot('leaderboard-players.json')
+ if (playerLeaderboard) boot.playerLeaderboard = playerLeaderboard
+ } else if (pathname === '/battle-logs' || pathname === '/live') {
+ const live = readPublicDataSnapshot('recent-games.json')
+ const leaderboard = readPublicDataSnapshot('leaderboard-teams.json')
+ if (live) boot.live = live
+ if (leaderboard) boot.leaderboard = leaderboard
+ }
+
+ return Object.keys(boot).length ? boot : null
+}
+
function htmlWithSeo(req, data) {
const origin = pagePublicOrigin(req)
let pathname = '/'
@@ -2301,6 +2338,7 @@ function htmlWithSeo(req, data) {
.replaceAll('__SEO_ROBOTS__', escapeHtml(seo.robots))
.replaceAll('__SEO_CANONICAL__', escapeHtml(canonicalUrl))
.replaceAll('__SEO_JSON_LD__', routeStructuredData(origin, seo, canonicalUrl).replace(/