From 5b19a49ffb16616dc5b1f11a7be479fa85ca7bcb Mon Sep 17 00:00:00 2001
From: "2569718930@qq.com" <2569718930@qq.com>
Date: Tue, 26 May 2026 08:23:33 +0800
Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E5=B1=8F=E4=BC=98=E5=8C=96=EF=BC=9Asc?=
=?UTF-8?q?an=20terminal=20=E5=8A=A0=20s-maxage=20=E7=BC=93=E5=AD=98=20+?=
=?UTF-8?q?=20=E8=90=BD=E5=9C=B0=E9=A1=B5=E9=A2=84=E5=8A=A0=E8=BD=BD=20API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. /api/scan/terminal 返回 Cache-Control: public, s-maxage=30, stale-while-revalidate=120
→ pre-warm 期间也能返回缓存,白屏 20s 变 1s
2. 落地页 2s 后静默 prefetch /api/cities,预热后端进程和连接池
3. metadata 加 preconnect api.polyweather.top
---
frontend/app/page.tsx | 11 +++++-
.../landing/PreloadTerminalData.tsx | 37 +++++++++++++++++++
web/routers/scan.py | 9 ++++-
3 files changed, 55 insertions(+), 2 deletions(-)
create mode 100644 frontend/components/landing/PreloadTerminalData.tsx
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
index f293cb9c..f890c2bf 100644
--- a/frontend/app/page.tsx
+++ b/frontend/app/page.tsx
@@ -1,11 +1,15 @@
import type { Metadata } from "next";
import { redirect } from "next/navigation";
+import { PreloadTerminalData } from "@/components/landing/PreloadTerminalData";
import { InstitutionalLandingPage } from "@/components/landing/InstitutionalLandingPage";
export const metadata: Metadata = {
title: "PolyWeather | Institutional Weather Signal Intelligence",
description:
"PolyWeather is a paid professional weather-signal intelligence terminal with METAR evidence, DEB forecast blending, and AI decision cards.",
+ other: {
+ preconnect: "https://api.polyweather.top",
+ },
};
export default async function HomePage({
@@ -29,5 +33,10 @@ export default async function HomePage({
const qs = usp.toString();
redirect(`/auth/callback${qs ? `?${qs}` : ""}`);
}
- return ;
+ return (
+ <>
+
+
+ >
+ );
}
diff --git a/frontend/components/landing/PreloadTerminalData.tsx b/frontend/components/landing/PreloadTerminalData.tsx
new file mode 100644
index 00000000..5ae8c678
--- /dev/null
+++ b/frontend/components/landing/PreloadTerminalData.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { useEffect } from "react";
+
+/**
+ * Warms the backend cache for public API endpoints while the user reads
+ * the landing page, so the first terminal load after login is faster.
+ *
+ * - /api/cities (public, triggers Python process warmup and connection pool init)
+ * - scan terminal (protected — skipped here; prefetched by the terminal
+ * component's stagger loading after login)
+ */
+export function PreloadTerminalData() {
+ useEffect(() => {
+ if (typeof window === "undefined" || typeof fetch !== "function") return;
+
+ const controller = new AbortController();
+
+ // Delay 2s so the landing page critical assets load first
+ const t = setTimeout(
+ () =>
+ fetch("/api/cities", {
+ cache: "no-store",
+ headers: { Accept: "application/json" },
+ signal: controller.signal,
+ }).catch(() => {}),
+ 2000,
+ );
+
+ return () => {
+ controller.abort();
+ clearTimeout(t);
+ };
+ }, []);
+
+ return null;
+}
diff --git a/web/routers/scan.py b/web/routers/scan.py
index 5b77c26d..5ef3141d 100644
--- a/web/routers/scan.py
+++ b/web/routers/scan.py
@@ -3,6 +3,7 @@
from __future__ import annotations
from fastapi import APIRouter, Request
+from fastapi.responses import JSONResponse
from web.services.scan_api import (
get_scan_terminal_ai_payload,
@@ -30,7 +31,7 @@ async def scan_terminal(
trading_region: str = "",
timezone_offset_seconds: int | None = None,
):
- return await get_scan_terminal_payload(
+ payload = await get_scan_terminal_payload(
request,
scan_mode=scan_mode,
min_price=min_price,
@@ -45,6 +46,12 @@ async def scan_terminal(
region=region or trading_region or None,
timezone_offset_seconds=timezone_offset_seconds,
)
+ return JSONResponse(
+ content=payload,
+ headers={
+ "Cache-Control": "public, s-maxage=30, stale-while-revalidate=120",
+ },
+ )
@router.post("/api/scan/terminal/ai")