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")