Selaa lähdekoodia

引入@t3-oss/env-nextjs 管理环境变量,使环境变量支持类型检查

fogwind 1 päivä sitten
vanhempi
commit
d6e4d9d969

+ 22 - 1
next.config.ts

@@ -1,8 +1,29 @@
-import { configHeader } from '@/utils/constants/server';
 import type { NextConfig } from "next";
+import "./src/env";
 
+const configHeader = [
+  // Security headers for all routes
+  {
+    source: "/:path*",
+    headers: [
+      { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
+      //{ key: "X-Frame-Options", value: "DENY" },
+      { key: "X-Content-Type-Options", value: "nosniff" },
+      // { key: "X-XSS-Protection", value: "1; mode=block" },
+      // {
+      //   key: "Permissions-Policy",
+      //   value:
+      //     "camera=(), microphone=(), geolocation=(), browsing-topics=()",
+      // },
+    ],
+  },
+  // Cache dynamic pages - shorter cache for frequently updated content
+  // Next.js static assets - long cache with immutable
+  // Public folder assets - long cache
+];
 const nextConfig: NextConfig = {
   output: "standalone",
+  transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"],
   devIndicators: false,
   reactStrictMode: true,
   typescript: {

+ 5 - 3
package.json

@@ -3,9 +3,9 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
-    "dev": "next dev -p 3001",
+    "dev": "next dev",
     "build": "eslint . && next build",
-    "start": "next start -p 3001",
+    "start": "next start",
     "lint": "eslint .",
     "lint:fix": "eslint . --fix",
     "package-version": "npx npm-check-updates"
@@ -38,6 +38,7 @@
     "@react-aria/visually-hidden": "^3.8.31",
     "@react-types/shared": "^3.33.1",
     "@reduxjs/toolkit": "^2.10.1",
+    "@t3-oss/env-nextjs": "^0.13.11",
     "big.js": "^7.0.1",
     "clsx": "^2.1.1",
     "framer-motion": "^12.23.24",
@@ -51,7 +52,8 @@
     "react-hook-form": "^7.66.1",
     "react-redux": "^9.2.0",
     "rxjs": "^7.8.2",
-    "swiper": "^12.1.3"
+    "swiper": "^12.1.3",
+    "zod": "^4.6.5"
   },
   "devDependencies": {
     "@tailwindcss/postcss": "^4",

+ 169 - 114
pnpm-lock.yaml

@@ -20,7 +20,7 @@ importers:
         version: 4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
       '@apollo/client-integration-nextjs':
         specifier: ^0.14.5
-        version: 0.14.5(@apollo/client@4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
+        version: 0.14.5(@apollo/client@4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
       '@heroicons/react':
         specifier: ^2.2.0
         version: 2.2.0(react@19.2.5)
@@ -93,6 +93,9 @@ importers:
       '@reduxjs/toolkit':
         specifier: ^2.10.1
         version: 2.12.0(react-redux@9.3.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1))(react@19.2.5)
+      '@t3-oss/env-nextjs':
+        specifier: ^0.13.11
+        version: 0.13.11(typescript@5.9.3)(zod@4.6.5)
       big.js:
         specifier: ^7.0.1
         version: 7.0.1
@@ -113,10 +116,10 @@ importers:
         version: 0.563.0(react@19.2.5)
       next:
         specifier: 16.3.4
-        version: 16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+        version: 16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       next-auth:
         specifier: ^4.24.13
-        version: 4.24.14(next@16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+        version: 4.24.14(next@16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       react:
         specifier: 19.2.5
         version: 19.2.5
@@ -135,6 +138,9 @@ importers:
       swiper:
         specifier: ^12.1.3
         version: 12.1.4
+      zod:
+        specifier: ^4.6.5
+        version: 4.6.5
     devDependencies:
       '@tailwindcss/postcss':
         specifier: ^4
@@ -156,10 +162,10 @@ importers:
         version: 10.1.0
       eslint:
         specifier: ^9
-        version: 9.39.4(jiti@2.7.0)
+        version: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       eslint-config-next:
         specifier: 16.3.4
-        version: 16.3.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
+        version: 16.3.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
       tailwindcss:
         specifier: ^4
         version: 4.3.0
@@ -1637,6 +1643,40 @@ packages:
   '@swc/helpers@0.5.23':
     resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
 
+  '@t3-oss/env-core@0.13.11':
+    resolution: {integrity: sha512-sM7GYY+KL7H/Hl0BE0inWfk3nRHZOLhmVn7sHGxaZt9FAR6KqREXAE+6TqKfiavfXmpRxO/OZ2QgKRd+oiBYRQ==}
+    peerDependencies:
+      arktype: ^2.1.0
+      typescript: '>=5.0.0'
+      valibot: ^1.0.0-beta.7 || ^1.0.0
+      zod: ^3.24.0 || ^4.0.0
+    peerDependenciesMeta:
+      arktype:
+        optional: true
+      typescript:
+        optional: true
+      valibot:
+        optional: true
+      zod:
+        optional: true
+
+  '@t3-oss/env-nextjs@0.13.11':
+    resolution: {integrity: sha512-NC+3j7YWgpzdFu1t5y/8wqibTK0lm5RS4bjXA1n8uwik3wIR4iZM4Fa+U2BaMa5k3Qk8RZiYhoAIX0WogmGkzg==}
+    peerDependencies:
+      arktype: ^2.1.0
+      typescript: '>=5.0.0'
+      valibot: ^1.0.0-beta.7 || ^1.0.0
+      zod: ^3.24.0 || ^4.0.0
+    peerDependenciesMeta:
+      arktype:
+        optional: true
+      typescript:
+        optional: true
+      valibot:
+        optional: true
+      zod:
+        optional: true
+
   '@tailwindcss/node@4.3.0':
     resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==}
 
@@ -3540,8 +3580,8 @@ packages:
     peerDependencies:
       zod: ^3.25.0 || ^4.0.0
 
-  zod@4.4.3:
-    resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
+  zod@4.6.5:
+    resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
 
 snapshots:
 
@@ -3580,11 +3620,11 @@ snapshots:
 
   '@alloc/quick-lru@5.2.0': {}
 
-  '@apollo/client-integration-nextjs@0.14.5(@apollo/client@4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)':
+  '@apollo/client-integration-nextjs@0.14.5(@apollo/client@4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)':
     dependencies:
       '@apollo/client': 4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
       '@apollo/client-react-streaming': 0.14.5(@apollo/client@4.2.0(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rxjs@7.8.2)
-      next: 16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+      next: 16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       react: 19.2.5
       rxjs: 7.8.2
     transitivePeerDependencies:
@@ -3627,20 +3667,20 @@ snapshots:
 
   '@babel/compat-data@7.29.3': {}
 
-  '@babel/core@7.29.0':
+  '@babel/core@7.29.0(supports-color@7.2.0)':
     dependencies:
       '@babel/code-frame': 7.29.0
       '@babel/generator': 7.29.1
       '@babel/helper-compilation-targets': 7.28.6
-      '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+      '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)
       '@babel/helpers': 7.29.2
       '@babel/parser': 7.29.3
       '@babel/template': 7.28.6
-      '@babel/traverse': 7.29.0
+      '@babel/traverse': 7.29.0(supports-color@7.2.0)
       '@babel/types': 7.29.0
       '@jridgewell/remapping': 2.3.5
       convert-source-map: 2.0.0
-      debug: 4.4.3
+      debug: 4.4.3(supports-color@7.2.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -3665,19 +3705,19 @@ snapshots:
 
   '@babel/helper-globals@7.28.0': {}
 
-  '@babel/helper-module-imports@7.28.6':
+  '@babel/helper-module-imports@7.28.6(supports-color@7.2.0)':
     dependencies:
-      '@babel/traverse': 7.29.0
+      '@babel/traverse': 7.29.0(supports-color@7.2.0)
       '@babel/types': 7.29.0
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+  '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0)':
     dependencies:
-      '@babel/core': 7.29.0
-      '@babel/helper-module-imports': 7.28.6
+      '@babel/core': 7.29.0(supports-color@7.2.0)
+      '@babel/helper-module-imports': 7.28.6(supports-color@7.2.0)
       '@babel/helper-validator-identifier': 7.28.5
-      '@babel/traverse': 7.29.0
+      '@babel/traverse': 7.29.0(supports-color@7.2.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -3704,7 +3744,7 @@ snapshots:
       '@babel/parser': 7.29.3
       '@babel/types': 7.29.0
 
-  '@babel/traverse@7.29.0':
+  '@babel/traverse@7.29.0(supports-color@7.2.0)':
     dependencies:
       '@babel/code-frame': 7.29.0
       '@babel/generator': 7.29.1
@@ -3712,7 +3752,7 @@ snapshots:
       '@babel/parser': 7.29.3
       '@babel/template': 7.28.6
       '@babel/types': 7.29.0
-      debug: 4.4.3
+      debug: 4.4.3(supports-color@7.2.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -3748,17 +3788,17 @@ snapshots:
 
   '@epic-web/invariant@1.0.0': {}
 
-  '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))':
+  '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))':
     dependencies:
-      eslint: 9.39.4(jiti@2.7.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       eslint-visitor-keys: 3.4.3
 
   '@eslint-community/regexpp@4.12.2': {}
 
-  '@eslint/config-array@0.21.2':
+  '@eslint/config-array@0.21.2(supports-color@7.2.0)':
     dependencies:
       '@eslint/object-schema': 2.1.7
-      debug: 4.4.3
+      debug: 4.4.3(supports-color@7.2.0)
       minimatch: 3.1.5
     transitivePeerDependencies:
       - supports-color
@@ -3771,10 +3811,10 @@ snapshots:
     dependencies:
       '@types/json-schema': 7.0.15
 
-  '@eslint/eslintrc@3.3.5':
+  '@eslint/eslintrc@3.3.5(supports-color@7.2.0)':
     dependencies:
       ajv: 6.15.0
-      debug: 4.4.3
+      debug: 4.4.3(supports-color@7.2.0)
       espree: 10.4.0
       globals: 14.0.0
       ignore: 5.3.2
@@ -4609,9 +4649,9 @@ snapshots:
 
   '@next/env@16.3.4': {}
 
-  '@next/eslint-plugin-next@16.3.4(eslint@9.39.4(jiti@2.7.0))':
+  '@next/eslint-plugin-next@16.3.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))':
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0))
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
       fast-glob: 3.3.1
     transitivePeerDependencies:
       - eslint
@@ -5581,6 +5621,18 @@ snapshots:
     dependencies:
       tslib: 2.8.1
 
+  '@t3-oss/env-core@0.13.11(typescript@5.9.3)(zod@4.6.5)':
+    optionalDependencies:
+      typescript: 5.9.3
+      zod: 4.6.5
+
+  '@t3-oss/env-nextjs@0.13.11(typescript@5.9.3)(zod@4.6.5)':
+    dependencies:
+      '@t3-oss/env-core': 0.13.11(typescript@5.9.3)(zod@4.6.5)
+    optionalDependencies:
+      typescript: 5.9.3
+      zod: 4.6.5
+
   '@tailwindcss/node@4.3.0':
     dependencies:
       '@jridgewell/remapping': 2.3.5
@@ -5693,15 +5745,15 @@ snapshots:
 
   '@types/use-sync-external-store@0.0.6': {}
 
-  '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)':
+  '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.2
-      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
+      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
       '@typescript-eslint/scope-manager': 8.59.4
-      '@typescript-eslint/type-utils': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
-      '@typescript-eslint/utils': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
+      '@typescript-eslint/type-utils': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
       '@typescript-eslint/visitor-keys': 8.59.4
-      eslint: 9.39.4(jiti@2.7.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       ignore: 7.0.5
       natural-compare: 1.4.0
       ts-api-utils: 2.5.0(typescript@5.9.3)
@@ -5709,23 +5761,23 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)':
+  '@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
     dependencies:
       '@typescript-eslint/scope-manager': 8.59.4
       '@typescript-eslint/types': 8.59.4
-      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3)
+      '@typescript-eslint/typescript-estree': 8.59.4(supports-color@7.2.0)(typescript@5.9.3)
       '@typescript-eslint/visitor-keys': 8.59.4
-      debug: 4.4.3
-      eslint: 9.39.4(jiti@2.7.0)
+      debug: 4.4.3(supports-color@7.2.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/project-service@8.59.4(typescript@5.9.3)':
+  '@typescript-eslint/project-service@8.59.4(supports-color@7.2.0)(typescript@5.9.3)':
     dependencies:
       '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.9.3)
       '@typescript-eslint/types': 8.59.4
-      debug: 4.4.3
+      debug: 4.4.3(supports-color@7.2.0)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
@@ -5739,13 +5791,13 @@ snapshots:
     dependencies:
       typescript: 5.9.3
 
-  '@typescript-eslint/type-utils@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)':
+  '@typescript-eslint/type-utils@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
     dependencies:
       '@typescript-eslint/types': 8.59.4
-      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3)
-      '@typescript-eslint/utils': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
-      debug: 4.4.3
-      eslint: 9.39.4(jiti@2.7.0)
+      '@typescript-eslint/typescript-estree': 8.59.4(supports-color@7.2.0)(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+      debug: 4.4.3(supports-color@7.2.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       ts-api-utils: 2.5.0(typescript@5.9.3)
       typescript: 5.9.3
     transitivePeerDependencies:
@@ -5753,28 +5805,28 @@ snapshots:
 
   '@typescript-eslint/types@8.59.4': {}
 
-  '@typescript-eslint/typescript-estree@8.59.4(typescript@5.9.3)':
+  '@typescript-eslint/typescript-estree@8.59.4(supports-color@7.2.0)(typescript@5.9.3)':
     dependencies:
-      '@typescript-eslint/project-service': 8.59.4(typescript@5.9.3)
+      '@typescript-eslint/project-service': 8.59.4(supports-color@7.2.0)(typescript@5.9.3)
       '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.9.3)
       '@typescript-eslint/types': 8.59.4
       '@typescript-eslint/visitor-keys': 8.59.4
-      debug: 4.4.3
+      debug: 4.4.3(supports-color@7.2.0)
       minimatch: 10.2.5
-      semver: 7.8.1
+      semver: 7.8.5
       tinyglobby: 0.2.16
       ts-api-utils: 2.5.0(typescript@5.9.3)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)':
+  '@typescript-eslint/utils@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0))
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
       '@typescript-eslint/scope-manager': 8.59.4
       '@typescript-eslint/types': 8.59.4
-      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3)
-      eslint: 9.39.4(jiti@2.7.0)
+      '@typescript-eslint/typescript-estree': 8.59.4(supports-color@7.2.0)(typescript@5.9.3)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
@@ -6100,13 +6152,17 @@ snapshots:
       es-errors: 1.3.0
       is-data-view: 1.0.2
 
-  debug@3.2.7:
+  debug@3.2.7(supports-color@7.2.0):
     dependencies:
       ms: 2.1.3
+    optionalDependencies:
+      supports-color: 7.2.0
 
-  debug@4.4.3:
+  debug@4.4.3(supports-color@7.2.0):
     dependencies:
       ms: 2.1.3
+    optionalDependencies:
+      supports-color: 7.2.0
 
   decimal.js@10.6.0: {}
 
@@ -6259,18 +6315,18 @@ snapshots:
 
   escape-string-regexp@4.0.0: {}
 
-  eslint-config-next@16.3.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3):
+  eslint-config-next@16.3.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3):
     dependencies:
-      '@next/eslint-plugin-next': 16.3.4(eslint@9.39.4(jiti@2.7.0))
-      eslint: 9.39.4(jiti@2.7.0)
-      eslint-import-resolver-node: 0.3.10
-      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
-      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
-      eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0))
-      eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
-      eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0))
+      '@next/eslint-plugin-next': 16.3.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
+      eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
+      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
+      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
+      eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
+      eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
+      eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
       globals: 16.4.0
-      typescript-eslint: 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
+      typescript-eslint: 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
     optionalDependencies:
       typescript: 5.9.3
     transitivePeerDependencies:
@@ -6279,52 +6335,52 @@ snapshots:
       - eslint-plugin-import-x
       - supports-color
 
-  eslint-import-resolver-node@0.3.10:
+  eslint-import-resolver-node@0.3.10(supports-color@7.2.0):
     dependencies:
-      debug: 3.2.7
+      debug: 3.2.7(supports-color@7.2.0)
       is-core-module: 2.16.2
       resolve: 2.0.0-next.7
     transitivePeerDependencies:
       - supports-color
 
-  eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)):
+  eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
     dependencies:
       '@nolyfill/is-core-module': 1.0.39
-      debug: 4.4.3
-      eslint: 9.39.4(jiti@2.7.0)
+      debug: 4.4.3(supports-color@7.2.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       get-tsconfig: 4.14.0
       is-bun-module: 2.0.0
       stable-hash: 0.0.5
       tinyglobby: 0.2.16
       unrs-resolver: 1.12.2
     optionalDependencies:
-      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
+      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
     transitivePeerDependencies:
       - supports-color
 
-  eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
+  eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
     dependencies:
-      debug: 3.2.7
+      debug: 3.2.7(supports-color@7.2.0)
     optionalDependencies:
-      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
-      eslint: 9.39.4(jiti@2.7.0)
-      eslint-import-resolver-node: 0.3.10
-      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
+      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
+      eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
+      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
+  eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
     dependencies:
       '@rtsao/scc': 1.1.0
       array-includes: 3.1.9
       array.prototype.findlastindex: 1.2.6
       array.prototype.flat: 1.3.3
       array.prototype.flatmap: 1.3.3
-      debug: 3.2.7
+      debug: 3.2.7(supports-color@7.2.0)
       doctrine: 2.1.0
-      eslint: 9.39.4(jiti@2.7.0)
-      eslint-import-resolver-node: 0.3.10
-      eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
+      eslint-import-resolver-node: 0.3.10(supports-color@7.2.0)
+      eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)
       hasown: 2.0.3
       is-core-module: 2.16.2
       is-glob: 4.0.3
@@ -6336,13 +6392,13 @@ snapshots:
       string.prototype.trimend: 1.0.9
       tsconfig-paths: 3.15.0
     optionalDependencies:
-      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
+      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
     transitivePeerDependencies:
       - eslint-import-resolver-typescript
       - eslint-import-resolver-webpack
       - supports-color
 
-  eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.7.0)):
+  eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0)):
     dependencies:
       aria-query: 5.3.2
       array-includes: 3.1.9
@@ -6352,7 +6408,7 @@ snapshots:
       axobject-query: 4.1.0
       damerau-levenshtein: 1.0.8
       emoji-regex: 9.2.2
-      eslint: 9.39.4(jiti@2.7.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       hasown: 2.0.3
       jsx-ast-utils: 3.3.5
       language-tags: 1.0.9
@@ -6361,18 +6417,18 @@ snapshots:
       safe-regex-test: 1.1.0
       string.prototype.includes: 2.0.1
 
-  eslint-plugin-react-hooks@7.1.1(eslint@9.39.4(jiti@2.7.0)):
+  eslint-plugin-react-hooks@7.1.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
     dependencies:
-      '@babel/core': 7.29.0
+      '@babel/core': 7.29.0(supports-color@7.2.0)
       '@babel/parser': 7.29.3
-      eslint: 9.39.4(jiti@2.7.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       hermes-parser: 0.25.1
-      zod: 4.4.3
-      zod-validation-error: 4.0.2(zod@4.4.3)
+      zod: 4.6.5
+      zod-validation-error: 4.0.2(zod@4.6.5)
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.7.0)):
+  eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0)):
     dependencies:
       array-includes: 3.1.9
       array.prototype.findlast: 1.2.5
@@ -6380,7 +6436,7 @@ snapshots:
       array.prototype.tosorted: 1.1.4
       doctrine: 2.1.0
       es-iterator-helpers: 1.3.2
-      eslint: 9.39.4(jiti@2.7.0)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       estraverse: 5.3.0
       hasown: 2.0.3
       jsx-ast-utils: 3.3.5
@@ -6405,14 +6461,14 @@ snapshots:
 
   eslint-visitor-keys@5.0.1: {}
 
-  eslint@9.39.4(jiti@2.7.0):
+  eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0):
     dependencies:
-      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0))
+      '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))
       '@eslint-community/regexpp': 4.12.2
-      '@eslint/config-array': 0.21.2
+      '@eslint/config-array': 0.21.2(supports-color@7.2.0)
       '@eslint/config-helpers': 0.4.2
       '@eslint/core': 0.17.0
-      '@eslint/eslintrc': 3.3.5
+      '@eslint/eslintrc': 3.3.5(supports-color@7.2.0)
       '@eslint/js': 9.39.4
       '@eslint/plugin-kit': 0.4.1
       '@humanfs/node': 0.16.8
@@ -6422,7 +6478,7 @@ snapshots:
       ajv: 6.15.0
       chalk: 4.1.2
       cross-spawn: 7.0.6
-      debug: 4.4.3
+      debug: 4.4.3(supports-color@7.2.0)
       escape-string-regexp: 4.0.0
       eslint-scope: 8.4.0
       eslint-visitor-keys: 4.2.1
@@ -6937,13 +6993,13 @@ snapshots:
 
   natural-compare@1.4.0: {}
 
-  next-auth@4.24.14(next@16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
+  next-auth@4.24.14(next@16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
     dependencies:
       '@babel/runtime': 7.29.2
       '@panva/hkdf': 1.2.1
       cookie: 0.7.2
       jose: 4.15.9
-      next: 16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+      next: 16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
       oauth: 0.9.15
       openid-client: 5.7.1
       preact: 10.29.2
@@ -6952,7 +7008,7 @@ snapshots:
       react-dom: 19.2.5(react@19.2.5)
       uuid: 8.3.2
 
-  next@16.3.4(@babel/core@7.29.0)(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
+  next@16.3.4(@babel/core@7.29.0(supports-color@7.2.0))(@types/node@20.19.41)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
     dependencies:
       '@next/env': 16.3.4
       '@swc/helpers': 0.5.23
@@ -6961,7 +7017,7 @@ snapshots:
       postcss: 8.5.23
       react: 19.2.5
       react-dom: 19.2.5(react@19.2.5)
-      styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.5)
+      styled-jsx: 5.1.6(@babel/core@7.29.0(supports-color@7.2.0))(react@19.2.5)
     optionalDependencies:
       '@next/swc-darwin-arm64': 16.3.4
       '@next/swc-darwin-x64': 16.3.4
@@ -7279,8 +7335,7 @@ snapshots:
 
   semver@7.8.1: {}
 
-  semver@7.8.5:
-    optional: true
+  semver@7.8.5: {}
 
   server-only@0.0.1: {}
 
@@ -7441,12 +7496,12 @@ snapshots:
 
   strip-json-comments@3.1.1: {}
 
-  styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.5):
+  styled-jsx@5.1.6(@babel/core@7.29.0(supports-color@7.2.0))(react@19.2.5):
     dependencies:
       client-only: 0.0.1
       react: 19.2.5
     optionalDependencies:
-      '@babel/core': 7.29.0
+      '@babel/core': 7.29.0(supports-color@7.2.0)
 
   supports-color@7.2.0:
     dependencies:
@@ -7545,13 +7600,13 @@ snapshots:
       possible-typed-array-names: 1.1.0
       reflect.getprototypeof: 1.0.10
 
-  typescript-eslint@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3):
+  typescript-eslint@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3):
     dependencies:
-      '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
-      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
-      '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3)
-      '@typescript-eslint/utils': 8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
-      eslint: 9.39.4(jiti@2.7.0)
+      '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+      '@typescript-eslint/parser': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+      '@typescript-eslint/typescript-estree': 8.59.4(supports-color@7.2.0)(typescript@5.9.3)
+      '@typescript-eslint/utils': 8.59.4(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)
+      eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0)
       typescript: 5.9.3
     transitivePeerDependencies:
       - supports-color
@@ -7686,8 +7741,8 @@ snapshots:
 
   yocto-queue@0.1.0: {}
 
-  zod-validation-error@4.0.2(zod@4.4.3):
+  zod-validation-error@4.0.2(zod@4.6.5):
     dependencies:
-      zod: 4.4.3
+      zod: 4.6.5
 
-  zod@4.4.3: {}
+  zod@4.6.5: {}

+ 3 - 3
src/app/(public)/product/_components/ProductMedia.tsx

@@ -8,7 +8,7 @@ import { Pagination } from "swiper/modules";
 import type { Swiper as SwiperType } from "swiper";
 import type { PaginationOptions } from "swiper/types";
 import type { ProductMediaType } from "@/components/catalog/type";
-import { baseUrl, getImageUrl } from "@/utils/constants";
+import { getImageUrl } from "@/utils/constants";
 import { useState, useRef } from "react";
 
 export function ProductMedia({
@@ -56,7 +56,7 @@ export function ProductMedia({
           <SwiperSlide key={img.id}>
             <Image
               className="block w-full object-cover"
-              src={getImageUrl(img.publicPath, baseUrl)}
+              src={getImageUrl(img.publicPath)}
               width={390}
               height={520}
               loading="eager"
@@ -92,7 +92,7 @@ export function ProductMedia({
                 }}
               >
                 <Image
-                  src={getImageUrl(img.publicPath, baseUrl)}
+                  src={getImageUrl(img.publicPath)}
                   width={100}
                   height={130}
                   loading="eager"

+ 8 - 0
src/app/api/health/route.ts

@@ -0,0 +1,8 @@
+import { NextResponse } from "next/server";
+
+export async function GET() {
+  return NextResponse.json({
+    status: "ok",
+    version: process.env.APP_VERSION ?? "unknown", // APP_VERSION环境变量是docker注入的,不是在.env里配置的
+  });
+}

+ 6 - 8
src/app/api/revalidate/route.ts

@@ -1,9 +1,9 @@
-import { type NextRequest, NextResponse } from "next/server";
+import { NextResponse } from "next/server";
 import { headers } from "next/headers";
 import { revalidatePath } from "next/cache";
 
 
-async function revalidate(req: NextRequest): Promise<NextResponse> {
+async function revalidate(): Promise<NextResponse> {
   const collectionWebhooks = [
     "collections/create",
     "collections/delete",
@@ -15,13 +15,11 @@ async function revalidate(req: NextRequest): Promise<NextResponse> {
     "products/update",
   ];
   const topic = (await headers()).get("x-bagisto-topic") || "unknown";
-  const secret = req.nextUrl.searchParams.get("secret");
+
   const isCollectionUpdate = collectionWebhooks.includes(topic);
   const isProductUpdate = productWebhooks.includes(topic);
 
-  if (!secret || secret !== process.env.BAGISTO_REVALIDATION_SECRET) {
-    return NextResponse.json({ status: 200 });
-  }
+
 
   if (!isCollectionUpdate && !isProductUpdate) {
     return NextResponse.json({ status: 200, message: "No action needed" });
@@ -42,6 +40,6 @@ async function revalidate(req: NextRequest): Promise<NextResponse> {
 }
 
 
-export async function POST(req: NextRequest): Promise<NextResponse> {
-  return revalidate(req);
+export async function POST(): Promise<NextResponse> {
+  return revalidate();
 }

+ 14 - 13
src/app/layout.tsx

@@ -3,6 +3,7 @@ import type { Viewport } from 'next';
 import { cookies } from "next/headers";
 import Script from "next/script";
 import "./globals.css";
+import { env } from "@/env";
 import { GlobalProviders,ReduxProvider,PayPalWrapper,ConfigProvider } from "@/providers";
 import { generateMetadataForPage } from "@utils/helper";
 import { staticSeo } from "@utils/metadata";
@@ -68,21 +69,21 @@ export default async function RootLayout({
   children: React.ReactNode;
 }>) {
 
-  let paypalClientId = process.env.PAYPAL_PRODUCTION_CLIENT_ID;
-  if (process.env.APP_ENV !== "production") {
-    paypalClientId = process.env.PAYPAL_SANDBOX_CLIENT_ID;
-  }
-  if(paypalClientId === undefined) {
-    throw new Error('Paypal Client Id is not defined');
+  let paypalClientId = env.PAYPAL_PRODUCTION_CLIENT_ID;
+  if (env.APP_ENV !== "production") {
+    paypalClientId = env.PAYPAL_SANDBOX_CLIENT_ID;
   }
+  // if(paypalClientId === undefined) {
+  //   throw new Error('Paypal Client Id is not defined');
+  // }
   // DEFAULT_CHANNEL DEFAULT_CURRENCY DEFAULT_LOCAL 同时也会写入cookie 
-  const defaultChannel = process.env.DEFAULT_CHANNEL;
-  const defaultCurrency = process.env.DEFAULT_CURRENCY;
-  const defaultLocale = process.env.DEFAULT_LOCAL;
-  const storeName = process.env.STORE_NAME;
-  if(!defaultChannel || !defaultCurrency || !defaultLocale ||!storeName) {
-    throw new Error('Default Channel or Default Currency or Default Locale or store name is not defined');
-  }
+  const defaultChannel = env.DEFAULT_CHANNEL;
+  const defaultCurrency = env.DEFAULT_CURRENCY;
+  const defaultLocale = env.DEFAULT_LOCAL;
+  const storeName = env.STORE_NAME;
+  // if(!defaultChannel || !defaultCurrency || !defaultLocale ||!storeName) {
+  //   throw new Error('Default Channel or Default Currency or Default Locale or store name is not defined');
+  // }
 
   const cookieStore = await cookies();
   ////////eruda调试/////////

+ 3 - 2
src/app/robots.ts

@@ -1,4 +1,5 @@
-import type { MetadataRoute } from 'next'
+import type { MetadataRoute } from 'next';
+import { env } from '@/env';
  
 // src/app/robots.ts 是 Next.js App Router 中用于生成 `robots.txt 文件的特殊路由文件。
 // 它的作用是告诉 搜索引擎爬虫(Googlebot、Bingbot 等)哪些页面可以抓取,哪些页面不要抓取,同时可以声明网站的 sitemap 地址。
@@ -9,6 +10,6 @@ export default function robots(): MetadataRoute.Robots {
       allow: '/',
       disallow:  ['/customer/*', '/checkout'],
     },
-    sitemap: `${process.env.NEXT_PUBLIC_SITE_URL}/sitemap.xml`,
+    sitemap: `${env.NEXT_PUBLIC_SITE_URL}/sitemap.xml`,
   }
 }

+ 2 - 2
src/components/catalog/product/ProductGridItems.tsx

@@ -1,4 +1,4 @@
-import { baseUrl, getImageUrl, NOT_IMAGE } from "@/utils/constants";
+import { getImageUrl } from "@/utils/constants";
 import { ProductCard } from "./ProductCard";
 
 export default function ProductGridItems({
@@ -8,7 +8,7 @@ export default function ProductGridItems({
 }) {
   return products.map((product: any, index: number) => {
 
-    const imageUrl = getImageUrl(product?.baseImageUrl, baseUrl, NOT_IMAGE);
+    const imageUrl = getImageUrl(product?.baseImageUrl);
     const price =
       product?.type === "configurable"
         ? product?.minimumPrice ?? "0"

+ 3 - 3
src/components/catalog/product/ProductsSection.tsx

@@ -1,7 +1,7 @@
 
-import { NOT_IMAGE } from "@/utils/constants";
+
 import Grid from "../../theme/ui/grid/Grid";
-import { baseUrl, getImageUrl } from "@/utils/constants";
+import { getImageUrl } from "@/utils/constants";
 import { ProductCard } from "./ProductCard";
 import type { ProductsSectionProps } from "../type";
 
@@ -18,7 +18,7 @@ export function ProductsSection({ title, description, products }: ProductsSectio
 
       <Grid className="grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
         {products.map((item, index) => {
-          const imageUrl = getImageUrl(item?.baseImageUrl, baseUrl, NOT_IMAGE);
+          const imageUrl = getImageUrl(item?.baseImageUrl);
           const ProductPrice =
             item?.type === "configurable"
               ? item?.minimumPrice ?? "0"

+ 2 - 1
src/components/error/ErrorBoundary.tsx

@@ -1,6 +1,7 @@
 "use client";
 
 import React, { Component, type ErrorInfo, type ReactNode } from "react";
+import { env } from "@/env";
 
 interface Props {
   children: ReactNode;
@@ -59,7 +60,7 @@ export class ErrorBoundary extends Component<Props, State> {
               Try Again
             </button>
           </div>
-          {process.env.NEXT_PUBLIC_APP_ENV !== "production" && this.state.error && (
+          {env.NEXT_PUBLIC_APP_ENV !== "production" && this.state.error && (
             <details className="mt-6 w-full max-w-2xl">
               <summary className="cursor-pointer text-sm text-gray-500">
                 Error Details (Development Only)

+ 3 - 2
src/components/home/ImageCarousel.tsx

@@ -3,6 +3,7 @@
 import { type FC, useState, useEffect, useCallback, useRef } from "react";
 import Link from "next/link";
 import Image from "next/image";
+import { env } from "@/env";
 import { Shimmer } from "@/components/common/Shimmer";
 
 interface ImageCarouselProps {
@@ -28,8 +29,8 @@ const ImageCarousel: FC<ImageCarouselProps> = ({ options }) => {
             return imagePath;
         }
 
-        const backendUrl = process.env.NEXT_PUBLIC_BAGISTO_ENDPOINT;
-        if (!backendUrl) return "";
+        const backendUrl = env.NEXT_PUBLIC_BAGISTO_ENDPOINT;
+
 
         const cleanPath = imagePath.startsWith('/') ? imagePath.slice(1) : imagePath;
         const cleanBase = backendUrl.endsWith('/') ? backendUrl.slice(0, -1) : backendUrl;

+ 2 - 4
src/components/home/ProductCarouselTheme.tsx

@@ -1,6 +1,6 @@
 import { ProductCard } from "@components/catalog/product/ProductCard";
 import type { ProductsSectionProps } from "@components/catalog/type";
-import { baseUrl, getImageUrl, NOT_IMAGE } from "@utils/constants";
+import { getImageUrl } from "@utils/constants";
 
 const Theme = ({ title, description, products }: ProductsSectionProps) => {
   return (
@@ -18,9 +18,7 @@ const Theme = ({ title, description, products }: ProductsSectionProps) => {
         <ul className="m-0 grid grid-cols-2 justify-center gap-5 lg:gap-11.5 p-0 xss:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
           {products.map((item, index) => {
             const imageUrl = getImageUrl(
-              item?.baseImageUrl,
-              baseUrl,
-              NOT_IMAGE,
+              item?.baseImageUrl
             );
             const ProductPrice =
               item?.type === "configurable"

+ 3 - 2
src/components/layout/footer/index.tsx

@@ -8,13 +8,14 @@ import type {
 import {
   GET_FOOTER
 } from "@/graphql";
+import { env } from "@/env";
 import LogoIcon from "@components/common/icons/LogoIcon";
 import FaceBookIcon from "@components/common/icons/social-icon/FaceBookIcon";
 import InstaGramIcon from "@components/common/icons/social-icon/InstaGramIcon";
 import TwitterIcon from "@components/common/icons/social-icon/TwitterIcon";
 import Subscribe from "./Subscribe";
 import FooterMenu from "./FooterMenu";
-const { COMPANY_NAME, SITE_NAME } = process.env;
+
 
 async function getFooterData(): Promise<ThemeCustomizationResult> {
  
@@ -46,7 +47,7 @@ export default async function Footer() {
   const skeleton =
     "w-full h-6 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700";
   const menu = await getFooterData();
-  const copyrightName = COMPANY_NAME || SITE_NAME || "";
+  const copyrightName = env.STORE_NAME;
 
 
   return (

+ 69 - 0
src/env.ts

@@ -0,0 +1,69 @@
+import { createEnv } from '@t3-oss/env-nextjs'
+import * as z from 'zod'
+/**
+ *  z.string()           // 必须是字符串
+    z.string().min(1)    // 字符串且不能是空字符串(最常用的"必填"写法)
+    z.string().url()     // 必须是合法 URL 格式(http://xxx 或 https://xxx)
+    z.string().length(3) // 字符串长度必须恰好是 3(比如货币代码 USD)
+    z.enum(['a', 'b'])   // 值必须是给定的几个字符串之一,多了写不了就用这个
+    z.string().startsWith('pk_') // 必须以某个前缀开头
+    .default('xxx')      // 如果没配置,给个默认值,不会校验失败
+    .optional()           // 允许是 undefined(不常用在这里,因为你都要求必填)
+ */
+export const env = createEnv({
+  /**
+   * 服务端变量:只能在服务端代码里访问(API Route / Server Component / Server Action)
+   * 客户端组件里引用会直接报错,防止密钥泄漏
+   */
+  server: {
+    // 业务环境标识,限定只能是这几个值之一
+    APP_ENV: z.enum(['development', 'test', 'production']),
+
+    // NextAuth 配置
+    NEXTAUTH_URL: z.url(),
+    // NextAuth 官方建议至少 32 位随机字符串,这里加个长度校验,防止手滑写太短
+    NEXTAUTH_SECRET: z.string().min(32, 'NEXTAUTH_SECRET 长度至少 32 位,建议用 openssl rand -base64 32 生成'),
+
+    // PayPal
+    PAYPAL_SANDBOX_CLIENT_ID: z.string().min(1),
+    PAYPAL_PRODUCTION_CLIENT_ID: z.string().min(1),
+
+    // 业务配置
+    COMPANY_NAME: z.string().min(1),
+    STORE_NAME: z.string().min(1),
+    DEFAULT_CURRENCY: z.string().length(3), // 货币代码固定 3 位,比如 USD、CNY
+    DEFAULT_LOCAL: z.string().min(2),        // 语言代码,比如 en、zh-CN
+    DEFAULT_CHANNEL: z.string().min(1),
+  },
+
+  /**
+   * 客户端变量:必须以 NEXT_PUBLIC_ 开头,会被打包进浏览器 bundle,不要放密钥
+   */
+  client: {
+    NEXT_PUBLIC_APP_ENV: z.enum(['development', 'test', 'production']).default('development'),
+    NEXT_PUBLIC_SITE_URL: z.url(),
+    NEXT_PUBLIC_BAGISTO_ENDPOINT: z.url(),
+    // storefront key 有固定前缀,顺手校验一下,配错了能第一时间发现
+    NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY: z.string().startsWith('pk_storefront_'),
+  },
+
+  /**
+   * 只需要列出 client 变量即可,Next.js >= 13.4.4 都用这个写法
+   */
+  experimental__runtimeEnv: {
+    NEXT_PUBLIC_APP_ENV: process.env.NEXT_PUBLIC_APP_ENV,
+    NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
+    NEXT_PUBLIC_BAGISTO_ENDPOINT: process.env.NEXT_PUBLIC_BAGISTO_ENDPOINT,
+    NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY: process.env.NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY,
+  },
+
+  /**
+   * docker build 或 CI 阶段如果没有真实环境变量,可以临时跳过校验
+   */
+  //   skipValidation: !!process.env.SKIP_ENV_VALIDATION,
+
+  /**
+   * .env 里如果某个变量写成空字符串(比如 XXX=),当作没配置处理,而不是校验通过一个空串
+   */
+  emptyStringAsUndefined: true,
+})

+ 2 - 1
src/lib/Airwallex/airwallexInit.ts

@@ -1,4 +1,5 @@
 import { init, type InitResult,createElement, type Payment } from '@airwallex/components-sdk';
+import { env } from "@/env";
 
 // ============================================================
 // Types
@@ -65,7 +66,7 @@ export function airwallexInit() {
 
     initPromise = init({
       env:
-        process.env.NEXT_PUBLIC_APP_ENV !== 'production'
+        env.NEXT_PUBLIC_APP_ENV !== 'production'
           ? 'demo'
           : 'prod',
 

+ 2 - 4
src/lib/ApolloClientServer.ts

@@ -6,6 +6,7 @@ import {
   ApolloClient,
   InMemoryCache,
 } from "@apollo/client-integration-nextjs";
+import { env } from "@/env";
 
 // apollo client 底层还是调的nextjs的fetch函数。
 // apollo client 的缓存策略是建立在nextjs缓存策略之上的,相当于两个沙盒
@@ -24,10 +25,7 @@ export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
       */
     });
     const authLink = new SetContextLink((prevContext) => {
-        const storefrontKey =
-            process.env.BAGISTO_STOREFRONT_KEY ||
-            process.env.NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY ||
-            "";
+        const storefrontKey = env.NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY;
 
         return {
             headers: {

+ 2 - 1
src/providers/PaymentSDKProvider.tsx

@@ -6,6 +6,7 @@ import {
   useContext,
   useState
 } from "react";
+import { env } from "@/env";
 
 type SDKLoadState = "none" | "loading" | "error" | "success";
 type SDKForPaymentMethod = "klarna" | "afterpay" | "clearpay" | "applepay";
@@ -23,7 +24,7 @@ interface PaymentContextProps {
 }
 
 const klarnaSDKURL = "https://x.klarnacdn.net/kp/lib/v1/api.js";
-const afterpaySDKURL = process.env.NEXT_PUBLIC_APP_ENV === "production" ? "https://portal.afterpay.com/afterpay.js" : "https://portal.sandbox.afterpay.com/afterpay.js";
+const afterpaySDKURL = env.NEXT_PUBLIC_APP_ENV === "production" ? "https://portal.afterpay.com/afterpay.js" : "https://portal.sandbox.afterpay.com/afterpay.js";
 const applepaySDKURL = "https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js";
 
 const PaymentSDKContext = createContext<PaymentContextProps|null>(null);

+ 2 - 1
src/providers/PaypalWrapper.tsx

@@ -1,5 +1,6 @@
 "use client";
 import { PayPalProvider } from "@paypal/react-paypal-js/sdk-v6";
+import { env } from "@/env";
 
 export function PayPalWrapper({ 
     children,
@@ -21,7 +22,7 @@ export function PayPalWrapper({
             "paypal-messages",
             "applepay-payments"
         ]}
-        environment={process.env.NEXT_PUBLIC_APP_ENV === "production" ? "production" : "sandbox"} // 上线改为 "production"
+        environment={env.NEXT_PUBLIC_APP_ENV === "production" ? "production" : "sandbox"} // 上线改为 "production"
         pageType="checkout"
     >
         {children}

+ 2 - 1
src/proxy/auth.ts

@@ -1,6 +1,7 @@
 import { NextResponse } from "next/server";
 import type { NextRequest } from "next/server";
 import { getToken } from "next-auth/jwt";
+import { env } from "@/env";
 export async function handleAuth(
     request: NextRequest,
     response: NextResponse
@@ -31,7 +32,7 @@ export async function handleAuth(
 
     const token = await getToken({
         req: request,
-        secret: process.env.NEXTAUTH_SECRET
+        secret: env.NEXTAUTH_SECRET
     });
 
     if (!token) {

+ 4 - 3
src/proxy/currency.ts

@@ -2,6 +2,7 @@ import type { NextRequest } from "next/server";
 import type { NextResponse } from "next/server";
 import {CURRENT_CURRENCY,CURRENT_LOCAL,CURRENT_CHANNEL} from "@/utils/constants";
 import {currencyCookieOptions} from "@/utils/constants/server";
+import { env } from "@/env";
 
 export function handleCurrency(
     request: NextRequest,
@@ -11,9 +12,9 @@ export function handleCurrency(
     const currency = request.cookies.get(CURRENT_CURRENCY);
     const local = request.cookies.get(CURRENT_LOCAL);
     const channel = request.cookies.get(CURRENT_CHANNEL);
-    const defaultCurrency = process.env.DEFAULT_CURRENCY;
-    const defaultLocal = process.env.DEFAULT_LOCAL;
-    const defaultChannel = process.env.DEFAULT_CHANNEL;
+    const defaultCurrency = env.DEFAULT_CURRENCY;
+    const defaultLocal = env.DEFAULT_LOCAL;
+    const defaultChannel = env.DEFAULT_CHANNEL;
 
     if (!currency && defaultCurrency) {
         

+ 2 - 1
src/utils/auth.ts

@@ -3,6 +3,7 @@ import CredentialsProvider from "next-auth/providers/credentials";
 import { bagistoFetch, /*restApiFetch*/} from "@/utils/bagisto";
 import { CUSTOMER_LOGIN } from "@/graphql/customer/mutations";
 import type {CustomerLoginResponse} from "@/types/customer/type";
+import { env } from "@/env";
 
 export const authOptions: NextAuthOptions = {
   session: {
@@ -97,7 +98,7 @@ export const authOptions: NextAuthOptions = {
     error: "/login",
   },
 
-  secret: process.env.NEXTAUTH_SECRET,
+  secret: env.NEXTAUTH_SECRET,
 };
 
 export const handler = NextAuth(authOptions);

+ 2 - 17
src/utils/bagisto/index.ts

@@ -19,6 +19,7 @@ import type {
   PageData,
 } from "@/types/theme/theme-customization";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
+import { env } from "@/env";
 
 
 type ExtractVariables<T> = T extends { variables: object }
@@ -30,7 +31,7 @@ type ExtractGraphqlData<T> = T extends { data: infer D } ? { data: D } : any;
 interface PageByUrlKeyResponse {
   pageByUrlKeypages?: PageData[];
 }
-const STOREFRONT_KEY = process.env.NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY;
+const STOREFRONT_KEY = env.NEXT_PUBLIC_BAGISTO_STOREFRONT_KEY;
 async function getBaseHeader() {
     /** 
      * Headers:
@@ -65,23 +66,7 @@ async function getBaseHeader() {
 export async function getAuthorizationToken(): Promise<{token: string | null; isGuest: boolean;}> {
   
   // 登录用户从nextAuth里获取
-  /*
-  const tokenCookie =
-      cookieStore.get("next-auth.session-token")
-      ?? cookieStore.get("__Secure-next-auth.session-token");
 
-  if (tokenCookie?.value) {
-      const token = await decode({
-          token: tokenCookie.value,
-          secret: process.env.NEXTAUTH_SECRET!,
-      });
-          console.log('getAuthorizationToken decode ===============',token);
-      return {
-        token: token?.accessToken ?? '',
-        isGuest: false,
-      };
-  }
-  */
   const authSession = await auth();
   const accessToken = authSession?.user?.accessToken;
   if (accessToken) {

+ 3 - 4
src/utils/constants.ts

@@ -1,3 +1,4 @@
+import { env } from '@/env';
 
 export const HIDDEN_PRODUCT_TAG = "nextjs-frontend-hidden";
 export const DEFAULT_OPTION = "Default Title";
@@ -15,8 +16,6 @@ export const PRODUCT_OFFER_TYPE = "AggregateOffer";
  * cookies constant
  */
 export const TOKEN = "token";
-export const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL;
-export const baseUrl = process.env.NEXT_PUBLIC_BAGISTO_ENDPOINT;
 
 
 // -----Pagination--------//
@@ -39,7 +38,7 @@ export const variants = {
 
 
 
-export function getImageUrl(url?: string, baseUrl?: string, fallback: string = NOT_IMAGE) {
+export function getImageUrl(url?: string, baseUrl: string = env.NEXT_PUBLIC_BAGISTO_ENDPOINT, fallback: string = NOT_IMAGE) {
   if (!url) return fallback;
 
   if (url.startsWith("http://") || url.startsWith("https://")) {
@@ -155,7 +154,7 @@ export const GUEST_COOKIE_OPTION = {
     encode: true,          // 默认编码,更加安全
     path: '/',
     // domain:,
-    secure: process.env.NEXT_PUBLIC_APP_ENV !== 'development',
+    secure: env.NEXT_PUBLIC_APP_ENV !== 'development',
     sameSite: sameSite,
 };
 

+ 5 - 23
src/utils/constants/server.ts

@@ -1,34 +1,16 @@
+import { env } from "@/env";
 import type { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies";
+
 export const BAGISTO_GRAPHQL_API_ENDPOINT = "/api/graphql";
 export const BAGISTO_REST_API_ENDPOINT = "/api";
-export const GRAPHQL_URL = `${(process.env.NEXT_PUBLIC_BAGISTO_ENDPOINT || '').replace(/\/$/, '')}${BAGISTO_GRAPHQL_API_ENDPOINT}`;
-export const REST_API_URL = `${(process.env.NEXT_PUBLIC_BAGISTO_ENDPOINT || '').replace(/\/$/, '')}${BAGISTO_REST_API_ENDPOINT}`;
+export const GRAPHQL_URL = `${(env.NEXT_PUBLIC_BAGISTO_ENDPOINT).replace(/\/$/, '')}${BAGISTO_GRAPHQL_API_ENDPOINT}`;
+export const REST_API_URL = `${(env.NEXT_PUBLIC_BAGISTO_ENDPOINT).replace(/\/$/, '')}${BAGISTO_REST_API_ENDPOINT}`;
 
 export const currencyCookieOptions = {
     path: '/', // 该cookie对那些路径有效,/ 表示所有路径
     maxAge: 60 * 60 * 24 * 365, // 过期时间 1年,到期后自动删除
     sameSite: 'lax', // 第三方网站请求你的站点时,浏览器是否携带 Cookie。
-    secure: process.env.APP_ENV !== 'development' // true 表示这个 Cookie 是否只能通过 HTTPS 发送
+    secure: env.APP_ENV !== 'development' // true 表示这个 Cookie 是否只能通过 HTTPS 发送
 } satisfies Partial<ResponseCookie>;
 
 
-export const configHeader = [
-  // Security headers for all routes
-  {
-    source: "/:path*",
-    headers: [
-      { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
-      //{ key: "X-Frame-Options", value: "DENY" },
-      { key: "X-Content-Type-Options", value: "nosniff" },
-      // { key: "X-XSS-Protection", value: "1; mode=block" },
-      // {
-      //   key: "Permissions-Policy",
-      //   value:
-      //     "camera=(), microphone=(), geolocation=(), browsing-topics=()",
-      // },
-    ],
-  },
-  // Cache dynamic pages - shorter cache for frequently updated content
-  // Next.js static assets - long cache with immutable
-  // Public folder assets - long cache
-];

+ 3 - 10
src/utils/helper.ts

@@ -2,7 +2,7 @@ import { ReadonlyURLSearchParams } from "next/navigation";
 import { type Metadata } from "next";
 import type { FilterDataTypes } from "@/types/types";
 import { isArray } from "./type-guards";
-import { BASE_URL, baseUrl } from "./constants";
+import { env } from '@/env';
 
 
 // Build revision identifier — emitted in <meta> for SSR cache validation.
@@ -20,13 +20,6 @@ export const createUrl = (
   return `${pathname}${queryString}`;
 };
 
-/**
- * Get base url
- * @returns string
- */
-export const getBaseUrl = (baseUrl: string) => {
-  return baseUrl ? `https://${baseUrl}` : "http://localhost:3001";
-};
 
 export const isCleanFilter = (
   filters: FilterDataTypes[],
@@ -139,7 +132,7 @@ export async function generateMetadataForPage(
     seo.description || fallback?.description || "Default page description.";
   const ogImage = seo.image || fallback?.image || "/default-og.png";
   const canonicalUrl =
-    seo.canonical || fallback?.canonical || `${BASE_URL}/${slug}`;
+    seo.canonical || fallback?.canonical || `${env.NEXT_PUBLIC_SITE_URL}/${slug}`;
 
   const otherMeta = {
     ...DEFAULT_OTHER,
@@ -148,7 +141,7 @@ export async function generateMetadataForPage(
   };
 
   return {
-    metadataBase: new URL(baseUrl || BASE_URL || "http://localhost:3001"),
+    metadataBase: new URL(env.NEXT_PUBLIC_SITE_URL),
 
     title,
     description,