diff --git a/app/HomeClient.tsx b/app/HomeClient.tsx
index e845f42..b5658a3 100644
--- a/app/HomeClient.tsx
+++ b/app/HomeClient.tsx
@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useState } from "react";
+import { HomePageSkeleton } from "@/app/components/HomePageSkeleton";
import { triggerPay } from "@/app/lib/triggerPay";
import { getPayEnv, getDeviceFromEnv } from "@/app/lib/payEnv";
import { usePayStatusPoll } from "@/app/lib/usePayStatusPoll";
@@ -753,7 +754,7 @@ export default function HomeClient() {
/>
>
) : (
-
+
)}
>
diff --git a/app/HomeClientLoader.tsx b/app/HomeClientLoader.tsx
index 408e1a2..3d7f80d 100644
--- a/app/HomeClientLoader.tsx
+++ b/app/HomeClientLoader.tsx
@@ -1,12 +1,11 @@
"use client";
import dynamic from "next/dynamic";
+import { HomePageSkeleton } from "./components/HomePageSkeleton";
const HomeClient = dynamic(() => import("./HomeClient"), {
ssr: false,
- loading: () => (
-
- ),
+ loading: () => ,
});
export function HomeClientLoader() {
diff --git a/app/components/HomePageSkeleton.tsx b/app/components/HomePageSkeleton.tsx
new file mode 100644
index 0000000..f6c17b8
--- /dev/null
+++ b/app/components/HomePageSkeleton.tsx
@@ -0,0 +1,63 @@
+function SkeletonBlocks() {
+ return (
+
+ );
+}
+
+type HomePageSkeletonProps = {
+ className?: string;
+ /** full:整页含顶栏;main:仅主内容区(已包在 VipLayout 内时用) */
+ variant?: "full" | "main";
+};
+
+/**
+ * 首页骨架(Server/Client 均可引用),保证首屏 HTML 即有可见结构,避免白屏。
+ */
+export function HomePageSkeleton({
+ className = "",
+ variant = "full",
+}: HomePageSkeletonProps) {
+ const main = (
+
+
+
+ );
+
+ if (variant === "main") {
+ return (
+
+ {main}
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/app/https-upgrade.ts b/app/https-upgrade.ts
new file mode 100644
index 0000000..9d1180a
--- /dev/null
+++ b/app/https-upgrade.ts
@@ -0,0 +1,12 @@
+/**
+ * 微信等环境可能以 http 打开 vip 域名,导致 Cookie/支付与 https 不一致。
+ * 在任意 JS 执行前立即跳转到 https(middleware 之外的兜底)。
+ */
+export const httpsUpgradeScript = `
+(function(){
+ var h=location.hostname;
+ if(h!=="vip.hackrobot.cn"&&h!=="www.vip.hackrobot.cn")return;
+ if(location.protocol!=="http:")return;
+ location.replace("https://"+h+location.pathname+location.search+location.hash);
+})();
+`;
diff --git a/app/layout.tsx b/app/layout.tsx
index 657dd82..b5b9ee4 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import "./globals.css";
import { ThemeProvider } from "./providers/ThemeProvider";
+import { httpsUpgradeScript } from "./https-upgrade";
import { themeInitScript } from "./theme-init";
export const metadata: Metadata = {
@@ -16,6 +17,7 @@ export default function RootLayout({
return (
+
{children}
diff --git a/app/page.tsx b/app/page.tsx
index f788218..7801860 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,5 +1,16 @@
+import { HomePageSkeleton } from "./components/HomePageSkeleton";
import { HomeClientLoader } from "./HomeClientLoader";
export default function Page() {
- return ;
+ return (
+
+ {/* SSR 即输出骨架,JS 未执行前也不白屏 */}
+
+
+
+
+
+
+
+ );
}
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..b7e72a5
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,32 @@
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+
+/** 生产域名:微信内访问无协议或 http 时常异常,统一 308 到 HTTPS */
+const FORCE_HTTPS_HOSTS = new Set(["vip.hackrobot.cn", "www.vip.hackrobot.cn"]);
+
+export function middleware(request: NextRequest) {
+ const rawHost = request.headers.get("host");
+ const host = rawHost?.split(":")[0]?.toLowerCase() ?? "";
+ if (!host || !FORCE_HTTPS_HOSTS.has(host)) {
+ return NextResponse.next();
+ }
+
+ const forwarded = request.headers.get("x-forwarded-proto");
+ const proto = forwarded?.split(",")[0]?.trim().toLowerCase();
+ const url = request.nextUrl.clone();
+
+ const isHttp =
+ proto === "http" ||
+ (proto === undefined && url.protocol === "http:");
+
+ if (isHttp) {
+ url.protocol = "https:";
+ return NextResponse.redirect(url, 308);
+ }
+
+ return NextResponse.next();
+}
+
+export const config = {
+ matcher: ["/:path*"],
+};