From 342c706552409151741140b652ff75597e79a7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Fri, 13 Mar 2026 14:14:54 +0800 Subject: [PATCH] fix: resolve hydration mismatch by moving config fetch to server-side --- src/app/(marketing)/home-content.tsx | 93 ++++++++++++++++++++ src/app/(marketing)/page.tsx | 126 +++++---------------------- 2 files changed, 115 insertions(+), 104 deletions(-) create mode 100644 src/app/(marketing)/home-content.tsx diff --git a/src/app/(marketing)/home-content.tsx b/src/app/(marketing)/home-content.tsx new file mode 100644 index 0000000..90ee9e6 --- /dev/null +++ b/src/app/(marketing)/home-content.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useEffect } from 'react'; +import { useSearchParams } from 'next/navigation'; +import dynamic from 'next/dynamic'; +import { HeroSection } from "@/components/sections/hero-section"; +import { SectionSkeleton } from "@/components/ui/loading-skeleton"; + +const ServicesSection = dynamic( + () => import('@/components/sections/services-section').then(mod => ({ default: mod.ServicesSection })), + { + loading: () => , + ssr: false + } +); + +const ProductsSection = dynamic( + () => import('@/components/sections/products-section').then(mod => ({ default: mod.ProductsSection })), + { + loading: () => , + ssr: false + } +); + +const CasesSection = dynamic( + () => import('@/components/sections/cases-section').then(mod => ({ default: mod.CasesSection })), + { + loading: () => , + ssr: false + } +); + +const AboutSection = dynamic( + () => import('@/components/sections/about-section').then(mod => ({ default: mod.AboutSection })), + { + loading: () => , + ssr: false + } +); + +const NewsSection = dynamic( + () => import('@/components/sections/news-section').then(mod => ({ default: mod.NewsSection })), + { + loading: () => , + ssr: false + } +); + +interface SiteConfig { + feature_services?: { enabled: boolean; items: string[] }; + feature_products?: { enabled: boolean; showPricing: boolean; featuredProducts: string[] }; + feature_news?: { enabled: boolean; displayCount: number; categories: string[]; sortOrder: 'asc' | 'desc' }; +} + +interface HomeContentProps { + config: SiteConfig; +} + +function HomeContent({ config }: HomeContentProps) { + const searchParams = useSearchParams(); + + useEffect(() => { + const section = searchParams.get('section'); + if (section) { + const timer = setTimeout(() => { + const targetElement = document.getElementById(section); + if (targetElement) { + targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, 100); + + return () => clearTimeout(timer); + } + return undefined; + }, [searchParams]); + + const showServices = config.feature_services?.enabled !== false; + const showProducts = config.feature_products?.enabled !== false; + const showNews = config.feature_news?.enabled !== false; + + return ( +
+ + {showServices && } + {showProducts && } + + + {showNews && } +
+ ); +} + +export { HomeContent }; diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index 32889f9..5bde137 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -1,50 +1,8 @@ -"use client"; - -import { Suspense, useEffect, useState } from 'react'; -import { useSearchParams } from 'next/navigation'; -import dynamic from 'next/dynamic'; -import { HeroSection } from "@/components/sections/hero-section"; -import { SectionSkeleton } from "@/components/ui/loading-skeleton"; - -const ServicesSection = dynamic( - () => import('@/components/sections/services-section').then(mod => ({ default: mod.ServicesSection })), - { - loading: () => , - ssr: false - } -); - -const ProductsSection = dynamic( - () => import('@/components/sections/products-section').then(mod => ({ default: mod.ProductsSection })), - { - loading: () => , - ssr: false - } -); - -const CasesSection = dynamic( - () => import('@/components/sections/cases-section').then(mod => ({ default: mod.CasesSection })), - { - loading: () => , - ssr: false - } -); - -const AboutSection = dynamic( - () => import('@/components/sections/about-section').then(mod => ({ default: mod.AboutSection })), - { - loading: () => , - ssr: false - } -); - -const NewsSection = dynamic( - () => import('@/components/sections/news-section').then(mod => ({ default: mod.NewsSection })), - { - loading: () => , - ssr: false - } -); +import { Suspense } from 'react'; +import { db } from '@/db'; +import { siteConfig } from '@/db/schema'; +import { HomeContent } from './home-content'; +import { SectionSkeleton } from '@/components/ui/loading-skeleton'; interface SiteConfig { feature_services?: { enabled: boolean; items: string[] }; @@ -52,68 +10,28 @@ interface SiteConfig { feature_news?: { enabled: boolean; displayCount: number; categories: string[]; sortOrder: 'asc' | 'desc' }; } -function HomeContent() { - const searchParams = useSearchParams(); - const [config, setConfig] = useState({}); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchConfig = async () => { - try { - const res = await fetch('/api/config'); - const data = await res.json(); - if (data.success) { - setConfig(data.data); - } - } catch (error) { - console.error('获取配置失败:', error); - } finally { - setLoading(false); - } - }; - - fetchConfig(); - }, []); - - useEffect(() => { - const section = searchParams.get('section'); - if (section) { - const timer = setTimeout(() => { - const targetElement = document.getElementById(section); - if (targetElement) { - targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }, 100); - - return () => clearTimeout(timer); - } - return undefined; - }, [searchParams]); - - if (loading) { - return ; +async function getSiteConfig(): Promise { + try { + const allConfigs = await db.select().from(siteConfig); + + const configMap = allConfigs.reduce((acc, config) => { + acc[config.key] = config.value; + return acc; + }, {} as Record); + + return configMap as SiteConfig; + } catch (error) { + console.error('获取配置失败:', error); + return {}; } - - const showServices = config.feature_services?.enabled !== false; - const showProducts = config.feature_products?.enabled !== false; - const showNews = config.feature_news?.enabled !== false; - - return ( -
- - {showServices && } - {showProducts && } - - - {showNews && } -
- ); } -export default function HomePage() { +export default async function HomePage() { + const config = await getSiteConfig(); + return ( }> - + ); }