服务端渲染SSR优化

198 阅读13分钟

服务端渲染SSR优化

如果应用的首屏渲染时间较长,可以考虑使用服务端渲染(SSR)。

SSR 可以减少客户端渲染的工作量,并显著提升首屏加载速度。Next.js 是一个用于构建 React SSR 应用的优秀框架。

SSR 实现框架与最佳实践

Next.js 全面实现

Next.js 提供了多种渲染模式,包括 SSR、SSG、ISR 等,可以根据不同场景选择最优方案。

渲染模式对比
flowchart LR
    A[用户请求] --> B{页面类型}
    
    B -->|SSG| C[预渲染静态页面]
    C --> D[CDN 缓存]
    D --> E[极速响应]
    
    B -->|SSR| F[服务端实时渲染]
    F --> G[动态内容]
    G --> H[个性化响应]
    
    B -->|ISR| I[增量静态再生]
    I --> J[定时更新]
    J --> K[兼备性能和灵活性]
    
    B -->|CSR| L[客户端渲染]
    L --> M[交互体验]
    
    style C fill:#e1f5fe
    style F fill:#f3e5f5
    style I fill:#e8f5e8
    style L fill:#fff3e0
页面级别配置
// pages/products/[id].js - SSR 配置
export async function getServerSideProps(context) {
  const { params, req, res, query } = context;
  const productId = params.id;
  
  // 设置缓存头
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=60, stale-while-revalidate=300'
  );
  
  try {
    // 并行获取数据
    const [product, reviews, recommendations] = await Promise.all([
      fetchProduct(productId),
      fetchProductReviews(productId),
      fetchRecommendations(productId)
    ]);
    
    return {
      props: {
        product,
        reviews,
        recommendations,
        timestamp: Date.now(), // 用于调试
      },
    };
  } catch (error) {
    console.error('SSR Error:', error);
    
    // 错误处理
    if (error.status === 404) {
      return { notFound: true };
    }
    
    // 回退到客户端渲染
    return {
      props: {
        error: true,
        fallbackData: await getCachedProductData(productId),
      },
    };
  }
}

// pages/blog/[slug].js - SSG 配置
export async function getStaticProps({ params }) {
  const post = await fetchBlogPost(params.slug);
  
  if (!post) {
    return { notFound: true };
  }
  
  return {
    props: {
      post,
    },
    revalidate: 3600, // ISR: 每小时更新一次
  };
}

export async function getStaticPaths() {
  // 只预渲染热门文章
  const popularPosts = await fetchPopularBlogPosts();
  
  const paths = popularPosts.map((post) => ({
    params: { slug: post.slug },
  }));
  
  return {
    paths,
    fallback: 'blocking', // 其他页面首次访问时 SSR
  };
}
中间件配置
// middleware.js - 全局中间件
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';

export async function middleware(request) {
  const token = await getToken({ req: request });
  const { pathname } = request.nextUrl;
  
  // A/B 测试配置
  if (pathname.startsWith('/experiment')) {
    const variant = request.cookies.get('ab-variant')?.value || 
      (Math.random() > 0.5 ? 'A' : 'B');
    
    const response = NextResponse.next();
    response.cookies.set('ab-variant', variant, {
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 天
    });
    
    // 重写 URL 根据变体
    if (variant === 'B') {
      return NextResponse.rewrite(
        new URL(pathname.replace('/experiment', '/experiment-b'), request.url)
      );
    }
    
    return response;
  }
  
  // 地理位置重定向
  const country = request.geo?.country || 'US';
  if (pathname === '/' && country === 'CN') {
    return NextResponse.redirect(new URL('/cn', request.url));
  }
  
  // 身份验证
  if (pathname.startsWith('/admin') && !token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

自定义 SSR 解决方案

Express + React SSR
// server/app.js
import express from 'express';
import React from 'react';
import { renderToString, renderToPipeableStream } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import App from '../src/App';
import { createServerRenderer } from './renderer';

const app = express();
const PORT = process.env.PORT || 3000;

// 静态资源服务
app.use('/static', express.static('dist/static'));

// 服务端渲染中间件
app.get('*', async (req, res) => {
  try {
    const context = {};
    const store = createStore(rootReducer, initialState);
    
    // 数据预加载
    await preloadData(req.path, store.dispatch);
    
    const renderer = createServerRenderer({
      url: req.url,
      context,
      store,
    });
    
    // 流式渲染 (React 18+)
    const { pipe, abort } = renderToPipeableStream(
      renderer,
      {
        bootstrapScripts: ['/static/js/client.js'],
        onShellReady() {
          res.statusCode = context.statusCode || 200;
          res.setHeader('Content-Type', 'text/html');
          pipe(res);
        },
        onError(error) {
          console.error('SSR Error:', error);
          res.statusCode = 500;
          res.end('Server Error');
        },
      }
    );
    
    // 超时处理
    setTimeout(abort, 10000);
    
  } catch (error) {
    console.error('SSR Error:', error);
    res.status(500).send('Server Error');
  }
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
服务端渲染器
// server/renderer.js
import React from 'react';
import { StaticRouter } from 'react-router-dom/server';
import { Provider } from 'react-redux';
import { HelmetProvider } from 'react-helmet-async';
import App from '../src/App';

export function createServerRenderer({ url, context, store }) {
  const helmetContext = {};
  
  return (
    <HelmetProvider context={helmetContext}>
      <Provider store={store}>
        <StaticRouter location={url} context={context}>
          <App />
        </StaticRouter>
      </Provider>
    </HelmetProvider>
  );
}

// 数据预加载
export async function preloadData(path, dispatch) {
  const routes = [
    {
      path: '/',
      component: 'Home',
      loadData: () => dispatch(fetchHomeData()),
    },
    {
      path: '/products/:id',
      component: 'Product',
      loadData: (params) => Promise.all([
        dispatch(fetchProduct(params.id)),
        dispatch(fetchProductReviews(params.id)),
      ]),
    },
  ];
  
  const matchedRoute = routes.find(route => 
    matchPath(path, { path: route.path, exact: true })
  );
  
  if (matchedRoute && matchedRoute.loadData) {
    await matchedRoute.loadData();
  }
}
HTML 模板生成
// server/template.js
export function createHTMLTemplate({
  content,
  state,
  helmet,
  stylesheets = [],
  scripts = [],
  preloadLinks = [],
}) {
  return `
    <!DOCTYPE html>
    <html ${helmet.htmlAttributes.toString()}>
      <head>
        ${helmet.title.toString()}
        ${helmet.meta.toString()}
        ${helmet.link.toString()}
        
        <!-- 预加载资源 -->
        ${preloadLinks.join('\n        ')}
        
        <!-- 关键 CSS -->
        ${stylesheets.map(href => 
          `<link rel="stylesheet" href="${href}" />`
        ).join('\n        ')}
        
        <!-- 内联关键 CSS -->
        <style id="critical-css">
          /* 关键样式将在构建时提取 */
        </style>
        
        <!-- 结构化数据 -->
        ${helmet.script.toString()}
      </head>
      <body ${helmet.bodyAttributes.toString()}>
        <!-- 页面内容 -->
        <div id="root">${content}</div>
        
        <!-- 状态数据 -->
        <script>
          window.__INITIAL_STATE__ = ${JSON.stringify(state).replace(
            /</g,
            '\\u003c'
          )};
        </script>
        
        <!-- 加载客户端 JavaScript -->
        ${scripts.map(src => 
          `<script src="${src}" defer></script>`
        ).join('\n        ')}
        
        <!-- 非关键 CSS 延迟加载 -->
        <script>
          // 加载非关键 CSS
          const loadCSS = function(href) {
            const link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = href;
            document.head.appendChild(link);
          };
          
          // 页面加载完成后加载
          window.addEventListener('load', function() {
            loadCSS('/static/css/non-critical.css');
          });
        </script>
      </body>
    </html>
  `;
}

SSR 带来的主要收益有:

React SSR (Server-Side Rendering)即服务端渲染,在 React 应用中,SSR 可以带来多个方面的收益:

收益

提升首屏渲染速度

SSR 的核心优势是将原本在客户端执行的渲染工作转移到服务端,使用户可以更快地看到页面内容。

传统 CSR vs SSR 渲染流程对比
flowchart TD
    A[用户访问页面] --> B{渲染方式}
    B -->|CSR| C[下载空白HTML]
    C --> D[下载JS Bundle]
    D --> E[执行JS代码]
    E --> F[渲染组件]
    F --> G[显示完整页面]
    
    B -->|SSR| H[服务端渲染HTML]
    H --> I[返回完整HTML]
    I --> J[用户看到内容]
    J --> K[下载JS Bundle]
    K --> L[Hydration]
    L --> M[交互功能就绪]
    
    style C fill:#ffcccc
    style D fill:#ffcccc
    style E fill:#ffcccc
    style F fill:#ffcccc
    style H fill:#ccffcc
    style I fill:#ccffcc
    style J fill:#ccffcc
性能指标对比
指标CSRSSR提升幅度
FCP (First Contentful Paint)2.5s0.8s68%
LCP (Largest Contentful Paint)3.2s1.2s62.5%
TTI (Time to Interactive)4.1s2.8s31.7%
TTFB (Time to First Byte)200ms800ms-300%
Next.js SSR 实现示例
// pages/products/[id].js
import { GetServerSideProps } from 'next';

interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  images: string[];
}

interface ProductPageProps {
  product: Product;
  recommendations: Product[];
}

// 服务端渲染组件
export default function ProductPage({ product, recommendations }: ProductPageProps) {
  return (
    <div className="product-page">
      <div className="product-header">
        <h1>{product.name}</h1>
        <p className="price">${product.price}</p>
      </div>
      
      <div className="product-images">
        {product.images.map((image, index) => (
          <img 
            key={index}
            src={image} 
            alt={`${product.name} - ${index + 1}`}
            loading={index === 0 ? 'eager' : 'lazy'}
          />
        ))}
      </div>
      
      <div className="product-description">
        <p>{product.description}</p>
      </div>
      
      <div className="recommendations">
        <h3>推荐商品</h3>
        <div className="recommendation-grid">
          {recommendations.map(item => (
            <ProductCard key={item.id} product={item} />
          ))}
        </div>
      </div>
    </div>
  );
}

// 服务端数据获取
export const getServerSideProps: GetServerSideProps<ProductPageProps> = async ({ params, req }) => {
  const productId = params?.id as string;
  
  // 并行获取数据以优化性能
  const [productResponse, recommendationsResponse] = await Promise.all([
    fetch(`${process.env.API_BASE_URL}/api/products/${productId}`, {
      headers: {
        'User-Agent': req.headers['user-agent'] || '',
        'Accept': 'application/json',
      },
    }),
    fetch(`${process.env.API_BASE_URL}/api/products/${productId}/recommendations`, {
      headers: {
        'User-Agent': req.headers['user-agent'] || '',
      },
    })
  ]);
  
  if (!productResponse.ok) {
    return {
      notFound: true,
    };
  }
  
  const product = await productResponse.json();
  const recommendations = await recommendationsResponse.json();
  
  return {
    props: {
      product,
      recommendations: recommendations || [],
    },
  };
};
React 18 并发特性优化 SSR
// server/render.js
import { renderToPipeableStream } from 'react-dom/server';
import { Suspense } from 'react';

// 流式渲染实现
export function renderApp(url, res) {
  const { pipe, abort } = renderToPipeableStream(
    <Suspense fallback={<PageSkeleton />}>
      <App url={url} />
    </Suspense>,
    {
      bootstrapScripts: ['/client.js'],
      onShellReady() {
        // 立即发送 HTML shell
        res.statusCode = 200;
        res.setHeader('Content-type', 'text/html');
        pipe(res);
      },
      onError(err) {
        console.error('SSR Error:', err);
        res.statusCode = 500;
        res.end('Server Error');
      },
    }
  );
  
  // 5秒超时
  setTimeout(abort, 5000);
}

// components/PageSkeleton.js
function PageSkeleton() {
  return (
    <div className="page-skeleton">
      <div className="skeleton-header" />
      <div className="skeleton-content">
        <div className="skeleton-line" />
        <div className="skeleton-line" />
        <div className="skeleton-line short" />
      </div>
    </div>
  );
}

有利于 SEO(搜索引擎优化)

搜索引擎爬虫可以直接读取 SSR 生成的 HTML 内容,而不需要执行 JavaScript。这显著提升了网站的搜索排名和收录效果。

SEO 关键要素实现
// pages/_document.js - Next.js 文档组件
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang="zh-CN">
      <Head>
        {/* 基础 SEO 配置 */}
        <meta name="robots" content="index,follow" />
        <meta name="googlebot" content="index,follow" />
        <link rel="canonical" href={process.env.NEXT_PUBLIC_SITE_URL} />
        
        {/* 结构化数据 */}
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{
            __html: JSON.stringify({
              "@context": "https://schema.org",
              "@type": "WebSite",
              "name": "电商平台",
              "url": process.env.NEXT_PUBLIC_SITE_URL,
              "potentialAction": {
                "@type": "SearchAction",
                "target": `${process.env.NEXT_PUBLIC_SITE_URL}/search?q={search_term_string}`,
                "query-input": "required name=search_term_string"
              }
            })
          }}
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}
// pages/products/[slug].js - 产品页面 SEO 优化
import Head from 'next/head';
import { GetServerSideProps } from 'next';

interface ProductSEOProps {
  product: {
    id: string;
    name: string;
    description: string;
    price: number;
    images: string[];
    category: string;
    brand: string;
    sku: string;
    availability: 'InStock' | 'OutOfStock';
    reviews: {
      averageRating: number;
      reviewCount: number;
    };
  };
}

export default function ProductPage({ product }: ProductSEOProps) {
  // 生成结构化数据
  const productStructuredData = {
    "@context": "https://schema.org",
    "@type": "Product",
    "name": product.name,
    "description": product.description,
    "sku": product.sku,
    "brand": {
      "@type": "Brand",
      "name": product.brand
    },
    "category": product.category,
    "image": product.images,
    "offers": {
      "@type": "Offer",
      "price": product.price,
      "priceCurrency": "CNY",
      "availability": `https://schema.org/${product.availability}`,
      "seller": {
        "@type": "Organization",
        "name": "电商平台"
      }
    },
    "aggregateRating": {
      "@type": "AggregateRating",
      "ratingValue": product.reviews.averageRating,
      "reviewCount": product.reviews.reviewCount
    }
  };

  return (
    <>
      <Head>
        {/* 基础 Meta 标签 */}
        <title>{`${product.name} - ${product.brand} | 电商平台`}</title>
        <meta 
          name="description" 
          content={`${product.description.substring(0, 160)}...`}
        />
        <meta name="keywords" content={`${product.name}, ${product.brand}, ${product.category}`} />
        
        {/* Open Graph 标签 */}
        <meta property="og:type" content="product" />
        <meta property="og:title" content={`${product.name} - ${product.brand}`} />
        <meta property="og:description" content={product.description} />
        <meta property="og:image" content={product.images[0]} />
        <meta property="og:url" content={`${process.env.NEXT_PUBLIC_SITE_URL}/products/${product.id}`} />
        <meta property="product:price:amount" content={product.price.toString()} />
        <meta property="product:price:currency" content="CNY" />
        
        {/* Twitter Card */}
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:title" content={`${product.name} - ${product.brand}`} />
        <meta name="twitter:description" content={product.description} />
        <meta name="twitter:image" content={product.images[0]} />
        
        {/* 结构化数据 */}
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{
            __html: JSON.stringify(productStructuredData)
          }}
        />
        
        {/* Canonical URL */}
        <link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/products/${product.id}`} />
      </Head>
      
      <div className="product-page">
        {/* 产品内容 */}
        <h1>{product.name}</h1>
        <p>{product.description}</p>
        {/* 其他产品信息 */}
      </div>
    </>
  );
}

export const getServerSideProps: GetServerSideProps = async ({ params, res }) => {
  const productId = params?.slug as string;
  
  try {
    const response = await fetch(`${process.env.API_BASE_URL}/api/products/${productId}`);
    
    if (!response.ok) {
      return { notFound: true };
    }
    
    const product = await response.json();
    
    // 设置缓存头以优化 SEO 和性能
    res.setHeader(
      'Cache-Control',
      'public, s-maxage=86400, stale-while-revalidate=604800'
    );
    
    return {
      props: {
        product,
      },
    };
  } catch (error) {
    console.error('Failed to fetch product:', error);
    return { notFound: true };
  }
};
动态 Sitemap 生成
// pages/sitemap.xml.js
import { GetServerSideProps } from 'next';

const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL;

function generateSiteMap(products) {
  return `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      <!-- 静态页面 -->
      <url>
        <loc>${SITE_URL}</loc>
        <lastmod>${new Date().toISOString()}</lastmod>
        <changefreq>daily</changefreq>
        <priority>1.0</priority>
      </url>
      <url>
        <loc>${SITE_URL}/about</loc>
        <lastmod>${new Date().toISOString()}</lastmod>
        <changefreq>monthly</changefreq>
        <priority>0.8</priority>
      </url>
      
      <!-- 产品页面 -->
      ${products
        .map(({ id, updatedAt }) => {
          return `
            <url>
              <loc>${SITE_URL}/products/${id}</loc>
              <lastmod>${updatedAt}</lastmod>
              <changefreq>weekly</changefreq>
              <priority>0.9</priority>
            </url>
          `;
        })
        .join('')}
    </urlset>
  `;
}

export const getServerSideProps: GetServerSideProps = async ({ res }) => {
  // 获取所有产品数据
  const productsResponse = await fetch(`${process.env.API_BASE_URL}/api/products/sitemap`);
  const products = await productsResponse.json();
  
  const sitemap = generateSiteMap(products);
  
  res.setHeader('Content-Type', 'text/xml');
  res.setHeader('Cache-Control', 'public, s-maxage=86400, stale-while-revalidate=604800');
  res.write(sitemap);
  res.end();
  
  return {
    props: {},
  };
};

export default function SiteMap() {
  // getServerSideProps 会在此之前返回响应
}

更好的社交媒体分享

SSR 允许在服务端动态生成 Open Graph 和 Twitter Card 元数据,使得在社交媒体平台分享链接时能够显示丰富的卡片信息。

Open Graph 等社交媒体元标签
// components/SocialMetaTags.js
import Head from 'next/head';

interface SocialMetaTagsProps {
  title: string;
  description: string;
  image: string;
  url: string;
  type?: 'website' | 'article' | 'product';
  author?: string;
  publishedTime?: string;
  modifiedTime?: string;
}

export function SocialMetaTags({
  title,
  description,
  image,
  url,
  type = 'website',
  author,
  publishedTime,
  modifiedTime
}: SocialMetaTagsProps) {
  const siteName = '电商平台';
  const twitterHandle = '@myecommerce';
  
  return (
    <Head>
      {/* Open Graph / Facebook */}
      <meta property="og:type" content={type} />
      <meta property="og:site_name" content={siteName} />
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={image} />
      <meta property="og:image:width" content="1200" />
      <meta property="og:image:height" content="630" />
      <meta property="og:image:alt" content={title} />
      <meta property="og:url" content={url} />
      <meta property="og:locale" content="zh_CN" />
      
      {/* 文章类型的额外元数据 */}
      {type === 'article' && (
        <>
          {author && <meta property="article:author" content={author} />}
          {publishedTime && <meta property="article:published_time" content={publishedTime} />}
          {modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
        </>
      )}
      
      {/* Twitter Card */}
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:site" content={twitterHandle} />
      <meta name="twitter:creator" content={twitterHandle} />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={image} />
      <meta name="twitter:image:alt" content={title} />
      
      {/* LinkedIn */}
      <meta property="linkedin:owner" content="company-id" />
      
      {/* 微信分享 */}
      <meta name="wechat:title" content={title} />
      <meta name="wechat:description" content={description} />
      <meta name="wechat:image" content={image} />
    </Head>
  );
}
动态社交分享图片生成
// pages/api/og-image.js - 动态生成分享图片
import { ImageResponse } from '@vercel/og';

export const config = {
  runtime: 'edge',
};

export default async function handler(req) {
  const { searchParams } = new URL(req.url);
  const title = searchParams.get('title') || '默认标题';
  const description = searchParams.get('description') || '默认描述';
  const productImage = searchParams.get('image');
  
  try {
    return new ImageResponse(
      (
        <div
          style={{
            height: '630px',
            width: '1200px',
            display: 'flex',
            flexDirection: 'column',
            backgroundColor: '#1f2937',
            color: 'white',
            fontFamily: 'Inter, sans-serif',
          }}
        >
          {/* 背景渐变 */}
          <div
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              right: 0,
              bottom: 0,
              background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
              opacity: 0.8,
            }}
          />
          
          {/* 内容区域 */}
          <div
            style={{
              display: 'flex',
              flexDirection: 'row',
              alignItems: 'center',
              justifyContent: 'space-between',
              padding: '80px',
              height: '100%',
              position: 'relative',
            }}
          >
            {/* 文字区域 */}
            <div
              style={{
                display: 'flex',
                flexDirection: 'column',
                maxWidth: '60%',
              }}
            >
              <h1
                style={{
                  fontSize: '64px',
                  fontWeight: 'bold',
                  lineHeight: 1.2,
                  marginBottom: '24px',
                }}
              >
                {title}
              </h1>
              <p
                style={{
                  fontSize: '24px',
                  opacity: 0.9,
                  lineHeight: 1.4,
                }}
              >
                {description}
              </p>
            </div>
            
            {/* 产品图片 */}
            {productImage && (
              <div
                style={{
                  width: '300px',
                  height: '300px',
                  borderRadius: '16px',
                  overflow: 'hidden',
                  boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
                }}
              >
                <img
                  src={productImage}
                  alt={title}
                  style={{
                    width: '100%',
                    height: '100%',
                    objectFit: 'cover',
                  }}
                />
              </div>
            )}
          </div>
          
          {/* Logo 和站点名 */}
          <div
            style={{
              position: 'absolute',
              bottom: '40px',
              left: '80px',
              display: 'flex',
              alignItems: 'center',
              fontSize: '20px',
              opacity: 0.8,
            }}
          >
            🛒 电商平台
          </div>
        </div>
      ),
      {
        width: 1200,
        height: 630,
        fonts: [
          {
            name: 'Inter',
            data: await fetch(
              new URL('../../assets/Inter-Bold.woff', import.meta.url)
            ).then((res) => res.arrayBuffer()),
            style: 'normal',
            weight: 700,
          },
        ],
      }
    );
  } catch (error) {
    console.error('OG Image generation failed:', error);
    return new Response('Failed to generate image', { status: 500 });
  }
}
社交分享预览组件
// components/SocialSharePreview.js
import { useState, useEffect } from 'react';

interface SocialSharePreviewProps {
  url: string;
  title: string;
  description: string;
  image: string;
}

export function SocialSharePreview({ url, title, description, image }: SocialSharePreviewProps) {
  const [previewData, setPreviewData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  
  // 预览社交分享效果
  const generatePreview = async () => {
    setIsLoading(true);
    try {
      // 可以调用第三方 API 来验证 OG 数据
      const response = await fetch(`/api/social-preview?url=${encodeURIComponent(url)}`);
      const data = await response.json();
      setPreviewData(data);
    } catch (error) {
      console.error('Failed to generate social preview:', error);
    } finally {
      setIsLoading(false);
    }
  };
  
  return (
    <div className="social-share-preview">
      <h3>社交分享预览</h3>
      
      {/* Facebook 预览 */}
      <div className="platform-preview facebook-preview">
        <h4>Facebook</h4>
        <div className="preview-card">
          <img src={image} alt={title} className="preview-image" />
          <div className="preview-content">
            <h5>{title}</h5>
            <p>{description}</p>
            <span className="preview-url">{new URL(url).hostname}</span>
          </div>
        </div>
      </div>
      
      {/* Twitter 预览 */}
      <div className="platform-preview twitter-preview">
        <h4>Twitter</h4>
        <div className="preview-card">
          <div className="preview-content">
            <h5>{title}</h5>
            <p>{description}</p>
            <span className="preview-url">{url}</span>
          </div>
          <img src={image} alt={title} className="preview-image" />
        </div>
      </div>
      
      {/* LinkedIn 预览 */}
      <div className="platform-preview linkedin-preview">
        <h4>LinkedIn</h4>
        <div className="preview-card">
          <img src={image} alt={title} className="preview-image" />
          <div className="preview-content">
            <h5>{title}</h5>
            <p>{description}</p>
            <span className="preview-url">{new URL(url).hostname}</span>
          </div>
        </div>
      </div>
      
      <button 
        onClick={generatePreview}
        disabled={isLoading}
        className="generate-preview-btn"
      >
        {isLoading ? '生成中...' : '生成预览'}
      </button>
    </div>
  );
}
社交分享辅助函数
// utils/socialSharing.js
export const socialPlatforms = {
  facebook: (url, title) => 
    `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`,
  
  twitter: (url, title, description) => 
    `https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}&hashtags=ecommerce`,
  
  linkedin: (url, title, description) => 
    `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`,
  
  wechat: (url) => {
    // 微信分享需要生成二维码
    return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(url)}`;
  },
  
  weibo: (url, title) => 
    `https://service.weibo.com/share/share.php?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`,
};

export function generateSocialShareUrls(url, title, description) {
  return Object.keys(socialPlatforms).reduce((acc, platform) => {
    acc[platform] = socialPlatforms[platform](url, title, description);
    return acc;
  }, {});
}

// 验证 OG 数据是否正确
export async function validateOGData(url) {
  try {
    const response = await fetch(url);
    const html = await response.text();
    
    const ogTitle = html.match(/<meta property="og:title" content="([^"]*)"/)?.[1];
    const ogDescription = html.match(/<meta property="og:description" content="([^"]*)"/)?.[1];
    const ogImage = html.match(/<meta property="og:image" content="([^"]*)"/)?.[1];
    
    return {
      title: ogTitle,
      description: ogDescription,
      image: ogImage,
      isValid: !!(ogTitle && ogDescription && ogImage)
    };
  } catch (error) {
    console.error('OG validation failed:', error);
    return { isValid: false };
  }
}

支持旧版浏览器

SSR 生成的静态 HTML 可以在不支持现代 JavaScript 特性的老旧浏览器中正常显示,提供更广泛的兼容性。

渐进式增强策略
// utils/browserSupport.js
export function getBrowserSupport() {
  // 检测浏览器支持情况
  const support = {
    es6: typeof Symbol !== 'undefined',
    es2017: typeof Promise !== 'undefined',
    modules: 'noModule' in document.createElement('script'),
    intersectionObserver: 'IntersectionObserver' in window,
    webp: false, // 需要异步检测
  };
  
  // WebP 支持检测
  const webpTest = new Image();
  webpTest.onload = webpTest.onerror = function() {
    support.webp = webpTest.height === 2;
  };
  webpTest.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
  
  return support;
}

export function loadPolyfills() {
  const polyfills = [];
  
  // Promise polyfill
  if (!window.Promise) {
    polyfills.push('https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js');
  }
  
  // IntersectionObserver polyfill
  if (!('IntersectionObserver' in window)) {
    polyfills.push('https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver');
  }
  
  // 动态加载 polyfills
  return Promise.all(
    polyfills.map(src => {
      return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;
        script.onload = resolve;
        script.onerror = reject;
        document.head.appendChild(script);
      });
    })
  );
}
分层加载策略
// pages/_app.js - Next.js App 组件
import { useEffect, useState } from 'react';
import { getBrowserSupport, loadPolyfills } from '../utils/browserSupport';

function MyApp({ Component, pageProps }) {
  const [isEnhanced, setIsEnhanced] = useState(false);
  const [browserSupport, setBrowserSupport] = useState(null);
  
  useEffect(() => {
    // 检测浏览器能力
    const support = getBrowserSupport();
    setBrowserSupport(support);
    
    // 加载必要的 polyfills
    loadPolyfills().then(() => {
      setIsEnhanced(true);
    }).catch(error => {
      console.warn('Failed to load polyfills:', error);
      // 即使 polyfills 加载失败,也要尝试启用增强功能
      setIsEnhanced(true);
    });
  }, []);
  
  // 为老旧浏览器提供基础版本
  if (!isEnhanced && typeof window !== 'undefined') {
    return (
      <div className="basic-layout">
        <noscript>
          <style dangerouslySetInnerHTML={{
            __html: `
              .enhanced-only { display: none !important; }
              .basic-fallback { display: block !important; }
            `
          }} />
        </noscript>
        <Component {...pageProps} />
      </div>
    );
  }
  
  return (
    <div className={`app-container ${browserSupport?.es6 ? 'modern' : 'legacy'}`}>
      <Component {...pageProps} browserSupport={browserSupport} />
    </div>
  );
}

export default MyApp;
兼容性组件封装
// components/CompatibleImage.js
import { useState, useEffect } from 'react';

interface CompatibleImageProps {
  src: string;
  webpSrc?: string;
  alt: string;
  width?: number;
  height?: number;
  loading?: 'lazy' | 'eager';
  fallback?: string;
}

export function CompatibleImage({ 
  src, 
  webpSrc, 
  alt, 
  width, 
  height, 
  loading = 'lazy',
  fallback 
}: CompatibleImageProps) {
  const [imageSrc, setImageSrc] = useState(src);
  const [hasError, setHasError] = useState(false);
  const [supportsWebP, setSupportsWebP] = useState(false);
  
  useEffect(() => {
    // WebP 支持检测
    if (webpSrc) {
      const webpTest = new Image();
      webpTest.onload = () => {
        if (webpTest.height === 2) {
          setSupportsWebP(true);
          setImageSrc(webpSrc);
        }
      };
      webpTest.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
    }
  }, [webpSrc]);
  
  const handleError = () => {
    setHasError(true);
    if (fallback) {
      setImageSrc(fallback);
    }
  };
  
  // 老旧浏览器不支持 loading 属性
  const imgProps = {
    src: imageSrc,
    alt,
    width,
    height,
    onError: handleError,
  };
  
  // 现代浏览器支持 lazy loading
  if ('loading' in HTMLImageElement.prototype) {
    imgProps.loading = loading;
  }
  
  return (
    <picture>
      {/* 现代浏览器使用 WebP */}
      {webpSrc && supportsWebP && (
        <source srcSet={webpSrc} type="image/webp" />
      )}
      
      {/* 回退到传统格式 */}
      <img 
        {...imgProps}
        style={{
          maxWidth: '100%',
          height: 'auto',
          ...(hasError && { backgroundColor: '#f0f0f0' })
        }}
      />
    </picture>
  );
}
CSS 兼容性策略
/* styles/compatibility.css */

/* 使用 @supports 规则提供回退方案 */

/* Flexbox 回退 */
.container {
  /* 老旧浏览器回退 */
  display: block;
  width: 100%;
}

@supports (display: flex) {
  .container {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
}

/* Grid 回退 */
.grid-container {
  /* 传统浏层方案 */
  overflow: hidden;
}

.grid-container .grid-item {
  float: left;
  width: 33.333%;
  padding: 10px;
  box-sizing: border-box;
}

@supports (display: grid) {
  .grid-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 20px;
    overflow: visible;
  }
  
  .grid-container .grid-item {
    float: none;
    width: auto;
    padding: 0;
  }
}

/* CSS 变量回退 */
.theme-colors {
  background-color: #ffffff;
  color: #333333;
}

@supports (color: var(--primary)) {
  .theme-colors {
    background-color: var(--bg-primary, #ffffff);
    color: var(--text-primary, #333333);
  }
}

/* 现代字体回退 */
.modern-font {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

@supports (font-variation-settings: normal) {
  .modern-font {
    font-family: 'Inter var', -apple-system, BlinkMacSystemFont, sans-serif;
    font-variation-settings: 'wght' 400;
  }
}

减少客户端负担

SSR 将复杂的渲染计算转移到服务端,减少了客户端的 CPU 和内存消耗,特别适合低端设备和移动端。

资源优化策略
// server/resourceOptimization.js
import { gzipSync, brotliCompressSync } from 'zlib';
import { minify } from 'html-minifier';

// HTML 压缩优化
export function optimizeHTML(html) {
  const minified = minify(html, {
    removeComments: true,
    removeRedundantAttributes: true,
    removeScriptTypeAttributes: true,
    removeStyleLinkTypeAttributes: true,
    sortClassName: true,
    useShortDoctype: true,
    collapseWhitespace: true,
    conservativeCollapse: true,
    preserveLineBreaks: false,
    minifyCSS: true,
    minifyJS: true,
  });
  
  return minified;
}

// 动态压缩选择
export function compressResponse(content, acceptEncoding) {
  // 优先使用 Brotli 压缩
  if (acceptEncoding.includes('br')) {
    return {
      content: brotliCompressSync(content),
      encoding: 'br'
    };
  }
  
  // 回退到 Gzip
  if (acceptEncoding.includes('gzip')) {
    return {
      content: gzipSync(content),
      encoding: 'gzip'
    };
  }
  
  return {
    content,
    encoding: 'identity'
  };
}

// 关键资源预加载
export function generatePreloadLinks(criticalResources) {
  return criticalResources.map(resource => {
    const { href, as, type, crossorigin } = resource;
    
    let link = `<link rel="preload" href="${href}" as="${as}"`;
    
    if (type) link += ` type="${type}"`;
    if (crossorigin) link += ` crossorigin="${crossorigin}"`;
    
    return link + '>';
  }).join('\n');
}
渐进式 Web 应用 (PWA) 策略
// next.config.js - PWA 配置
const withPWA = require('next-pwa');

module.exports = withPWA({
  pwa: {
    dest: 'public',
    disable: process.env.NODE_ENV === 'development',
    register: true,
    skipWaiting: true,
    runtimeCaching: [
      {
        urlPattern: /^https:\/\/api\.example\.com\/.*/i,
        handler: 'CacheFirst',
        options: {
          cacheName: 'api-cache',
          expiration: {
            maxEntries: 50,
            maxAgeSeconds: 300, // 5 分钟
          },
        },
      },
      {
        urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/i,
        handler: 'CacheFirst',
        options: {
          cacheName: 'images-cache',
          expiration: {
            maxEntries: 100,
            maxAgeSeconds: 86400, // 24 小时
          },
        },
      },
    ],
  },
  // 其他 Next.js 配置
  experimental: {
    modern: true,
  },
  // 打包优化
  webpack: (config, { isServer }) => {
    if (!isServer) {
      // 客户端 bundle 优化
      config.optimization.splitChunks = {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      };
    }
    return config;
  },
});
智能资源加载
// components/SmartResourceLoader.js
import { useState, useEffect, useCallback } from 'react';
import { useIntersectionObserver } from '../hooks/useIntersectionObserver';

interface SmartResourceLoaderProps {
  children: React.ReactNode;
  threshold?: number;
  rootMargin?: string;
  fallback?: React.ReactNode;
}

export function SmartResourceLoader({
  children,
  threshold = 0.1,
  rootMargin = '50px',
  fallback
}: SmartResourceLoaderProps) {
  const [shouldLoad, setShouldLoad] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  
  // 使用 Intersection Observer 检测可见性
  const [ref, isIntersecting] = useIntersectionObserver({
    threshold,
    rootMargin,
  });
  
  // 检测网络状态
  const [networkInfo, setNetworkInfo] = useState({
    effectiveType: '4g',
    saveData: false,
  });
  
  useEffect(() => {
    if ('connection' in navigator) {
      const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
      setNetworkInfo({
        effectiveType: connection.effectiveType || '4g',
        saveData: connection.saveData || false,
      });
      
      const handleConnectionChange = () => {
        setNetworkInfo({
          effectiveType: connection.effectiveType || '4g',
          saveData: connection.saveData || false,
        });
      };
      
      connection.addEventListener('change', handleConnectionChange);
      return () => connection.removeEventListener('change', handleConnectionChange);
    }
  }, []);
  
  // 智能加载策略
  useEffect(() => {
    if (isIntersecting && !shouldLoad && !isLoading) {
      // 根据网络条件决定是否加载
      const shouldLoadBasedOnNetwork = () => {
        if (networkInfo.saveData) return false; // 数据节省模式
        if (networkInfo.effectiveType === 'slow-2g') return false;
        if (networkInfo.effectiveType === '2g') return false;
        return true;
      };
      
      if (shouldLoadBasedOnNetwork()) {
        setIsLoading(true);
        // 模拟异步加载
        setTimeout(() => {
          setShouldLoad(true);
          setIsLoading(false);
        }, 100);
      }
    }
  }, [isIntersecting, shouldLoad, isLoading, networkInfo]);
  
  return (
    <div ref={ref}>
      {shouldLoad ? children : (fallback || <div className="loading-placeholder" />)}
      {isLoading && (
        <div className="loading-indicator">
          加载中...
        </div>
      )}
    </div>
  );
}

// hooks/useIntersectionObserver.js
import { useEffect, useRef, useState } from 'react';

export function useIntersectionObserver({
  threshold = 0,
  root = null,
  rootMargin = '0%',
}) {
  const [entry, setEntry] = useState(null);
  const [isIntersecting, setIsIntersecting] = useState(false);
  const elementRef = useRef(null);
  
  useEffect(() => {
    const element = elementRef.current;
    if (!element) return;
    
    const observer = new IntersectionObserver(
      ([entry]) => {
        setEntry(entry);
        setIsIntersecting(entry.isIntersecting);
      },
      { threshold, root, rootMargin }
    );
    
    observer.observe(element);
    
    return () => observer.unobserve(element);
  }, [threshold, root, rootMargin]);
  
  return [elementRef, isIntersecting, entry];
}
服务端渲染缓存策略
// server/caching.js
import Redis from 'ioredis';
import { LRUCache } from 'lru-cache';

const redis = new Redis(process.env.REDIS_URL);

// 多层缓存策略
class MultiLayerCache {
  constructor() {
    // 内存缓存 (L1)
    this.memoryCache = new LRUCache({
      max: 500,
      ttl: 1000 * 60 * 5, // 5 分钟
    });
    
    // Redis 缓存 (L2)
    this.redisCache = redis;
  }
  
  async get(key) {
    // 先检查内存缓存
    const memoryValue = this.memoryCache.get(key);
    if (memoryValue) {
      return memoryValue;
    }
    
    // 再检查 Redis 缓存
    const redisValue = await this.redisCache.get(key);
    if (redisValue) {
      const parsed = JSON.parse(redisValue);
      // 回填到内存缓存
      this.memoryCache.set(key, parsed);
      return parsed;
    }
    
    return null;
  }
  
  async set(key, value, ttl = 300) {
    // 同时设置两层缓存
    this.memoryCache.set(key, value);
    await this.redisCache.setex(key, ttl, JSON.stringify(value));
  }
  
  async invalidate(pattern) {
    // 清理匹配的缓存
    this.memoryCache.clear();
    
    const keys = await this.redisCache.keys(pattern);
    if (keys.length > 0) {
      await this.redisCache.del(...keys);
    }
  }
}

const cache = new MultiLayerCache();

// SSR 页面缓存中间件
export function ssrCacheMiddleware(options = {}) {
  const {
    ttl = 300,
    keyGenerator = (req) => `ssr:${req.url}`,
    shouldCache = () => true,
  } = options;
  
  return async (req, res, next) => {
    if (req.method !== 'GET' || !shouldCache(req)) {
      return next();
    }
    
    const cacheKey = keyGenerator(req);
    
    // 尝试从缓存获取
    const cached = await cache.get(cacheKey);
    if (cached) {
      res.setHeader('X-Cache', 'HIT');
      res.setHeader('Content-Type', 'text/html');
      return res.send(cached);
    }
    
    // 拦截响应
    const originalSend = res.send;
    res.send = function(html) {
      // 缓存响应
      cache.set(cacheKey, html, ttl);
      res.setHeader('X-Cache', 'MISS');
      return originalSend.call(this, html);
    };
    
    next();
  };
}

提升应用的可靠性

SSR 提供了更强的容错能力和稳定性,即使在 JavaScript 执行失败或网络状态不佳的情况下,用户仍能看到基本内容。

容错设计策略
// components/ErrorBoundary.js
import React from 'react';
import * as Sentry from '@sentry/nextjs';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null,
      errorInfo: null 
    };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    this.setState({
      error,
      errorInfo
    });
    
    // 上报错误到 Sentry
    Sentry.captureException(error, {
      contexts: {
        react: {
          componentStack: errorInfo.componentStack,
        },
      },
    });
    
    // 本地错误日志
    console.error('React Error Boundary caught an error:', error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      const { fallback: Fallback, level = 'page' } = this.props;
      
      // 自定义回退组件
      if (Fallback) {
        return <Fallback error={this.state.error} />;
      }
      
      // 根据错误级别返回不同的回退界面
      if (level === 'component') {
        return (
          <div className="error-fallback-component">
            <p>组件加载失败,请刷新页面重试。</p>
            <button onClick={() => window.location.reload()}>
              刷新页面
            </button>
          </div>
        );
      }
      
      return (
        <div className="error-fallback-page">
          <h2>页面加载遇到问题</h2>
          <p>抱歉,页面出现了一些问题。请尝试刷新页面或返回首页。</p>
          <div className="error-actions">
            <button onClick={() => window.location.reload()}>
              刷新页面
            </button>
            <button onClick={() => window.location.href = '/'}>
              返回首页
            </button>
          </div>
          
          {process.env.NODE_ENV === 'development' && (
            <details className="error-details">
              <summary>错误详情 (仅开发环境)</summary>
              <pre>{this.state.error && this.state.error.toString()}</pre>
              <pre>{this.state.errorInfo.componentStack}</pre>
            </details>
          )}
        </div>
      );
    }
    
    return this.props.children;
  }
}

export default ErrorBoundary;
网络失败处理
// hooks/useNetworkRecovery.js
import { useState, useEffect, useCallback } from 'react';

export function useNetworkRecovery() {
  const [isOnline, setIsOnline] = useState(true);
  const [retryCount, setRetryCount] = useState(0);
  const [isRetrying, setIsRetrying] = useState(false);
  
  useEffect(() => {
    const handleOnline = () => {
      setIsOnline(true);
      setRetryCount(0);
    };
    
    const handleOffline = () => {
      setIsOnline(false);
    };
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    // 初始化检查
    setIsOnline(navigator.onLine);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  const retryWithBackoff = useCallback(async (fn, maxRetries = 3) => {
    if (isRetrying) return;
    
    setIsRetrying(true);
    
    for (let i = 0; i < maxRetries; i++) {
      try {
        const result = await fn();
        setRetryCount(0);
        setIsRetrying(false);
        return result;
      } catch (error) {
        const delay = Math.min(1000 * Math.pow(2, i), 10000); // 指数退避,最大 10 秒
        setRetryCount(i + 1);
        
        if (i === maxRetries - 1) {
          setIsRetrying(false);
          throw error;
        }
        
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }, [isRetrying]);
  
  return {
    isOnline,
    retryCount,
    isRetrying,
    retryWithBackoff,
  };
}
稳健的 API 请求
// utils/resilientFetch.js
import { useNetworkRecovery } from '../hooks/useNetworkRecovery';

// 带有重试和缓存的 fetch 封装
export class ResilientFetch {
  constructor(options = {}) {
    this.baseURL = options.baseURL || '';
    this.timeout = options.timeout || 10000;
    this.retries = options.retries || 3;
    this.cache = new Map();
    this.retryDelay = options.retryDelay || 1000;
  }
  
  async fetch(url, options = {}) {
    const fullUrl = `${this.baseURL}${url}`;
    const cacheKey = `${fullUrl}_${JSON.stringify(options)}`;
    
    // 检查缓存
    if (options.useCache && this.cache.has(cacheKey)) {
      const cached = this.cache.get(cacheKey);
      if (Date.now() - cached.timestamp < (options.cacheTime || 300000)) {
        return cached.data;
      }
    }
    
    let lastError;
    
    for (let i = 0; i < this.retries; i++) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.timeout);
        
        const response = await fetch(fullUrl, {
          ...options,
          signal: controller.signal,
        });
        
        clearTimeout(timeoutId);
        
        if (!response.ok) {
          throw new Error(`HTTP Error: ${response.status}`);
        }
        
        const data = await response.json();
        
        // 缓存成功的响应
        if (options.useCache) {
          this.cache.set(cacheKey, {
            data,
            timestamp: Date.now()
          });
        }
        
        return data;
        
      } catch (error) {
        lastError = error;
        
        // 最后一次重试失败后抛出错误
        if (i === this.retries - 1) {
          // 尝试从缓存返回过期数据
          if (this.cache.has(cacheKey)) {
            console.warn('API 请求失败,返回缓存数据');
            return this.cache.get(cacheKey).data;
          }
          throw lastError;
        }
        
        // 等待一定时间后重试
        await new Promise(resolve => 
          setTimeout(resolve, this.retryDelay * Math.pow(2, i))
        );
      }
    }
  }
  
  // 清理过期缓存
  clearExpiredCache() {
    const now = Date.now();
    for (const [key, value] of this.cache.entries()) {
      if (now - value.timestamp > 3600000) { // 1 小时
        this.cache.delete(key);
      }
    }
  }
}

const apiClient = new ResilientFetch({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  timeout: 8000,
  retries: 3,
});

export default apiClient;
数据降级策略
// components/DataWithFallback.js
import { useState, useEffect } from 'react';
import apiClient from '../utils/resilientFetch';

interface DataWithFallbackProps {
  endpoint: string;
  fallbackData?: any;
  renderData: (data: any) => React.ReactNode;
  renderLoading?: () => React.ReactNode;
  renderError?: (error: Error) => React.ReactNode;
  renderFallback?: (fallbackData: any) => React.ReactNode;
}

export function DataWithFallback({
  endpoint,
  fallbackData,
  renderData,
  renderLoading,
  renderError,
  renderFallback
}: DataWithFallbackProps) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [usingFallback, setUsingFallback] = useState(false);
  
  useEffect(() => {
    let isMounted = true;
    
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const result = await apiClient.fetch(endpoint, {
          useCache: true,
          cacheTime: 300000, // 5 分钟缓存
        });
        
        if (isMounted) {
          setData(result);
          setUsingFallback(false);
        }
      } catch (err) {
        if (isMounted) {
          setError(err);
          
          // 使用回退数据
          if (fallbackData) {
            setData(fallbackData);
            setUsingFallback(true);
          }
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      isMounted = false;
    };
  }, [endpoint, fallbackData]);
  
  if (loading && !data) {
    return renderLoading ? renderLoading() : <div>加载中...</div>;
  }
  
  if (error && !data) {
    return renderError ? renderError(error) : (
      <div className="error-state">
        <p>数据加载失败</p>
        <button onClick={() => window.location.reload()}>
          重新加载
        </button>
      </div>
    );
  }
  
  if (usingFallback && renderFallback) {
    return renderFallback(data);
  }
  
  return (
    <>
      {renderData(data)}
      {usingFallback && (
        <div className="fallback-notice">
          ℹ️ 当前显示的是缓存数据
        </div>
      )}
    </>
  );
}

SSR 性能优化技巧

数据获取优化

并行数据加载
// 优化前:串行获取数据
export async function getServerSideProps({ params }) {
  const user = await fetchUser(params.userId);
  const posts = await fetchUserPosts(params.userId); // 等待上一个完成
  const followers = await fetchUserFollowers(params.userId); // 等待上一个完成
  
  return { props: { user, posts, followers } };
}

// 优化后:并行获取数据
export async function getServerSideProps({ params }) {
  const [user, posts, followers] = await Promise.all([
    fetchUser(params.userId),
    fetchUserPosts(params.userId),
    fetchUserFollowers(params.userId),
  ]);
  
  return { props: { user, posts, followers } };
}

// 高级优化:批量请求
export async function getServerSideProps({ params }) {
  // 使用 DataLoader 批量获取
  const [userWithCounts, posts] = await Promise.all([
    userLoader.load(params.userId), // 自动批量处理
    postLoader.loadMany(await getUserPostIds(params.userId)),
  ]);
  
  return {
    props: {
      user: userWithCounts,
      posts,
    },
  };
}
DataLoader 实现
// utils/dataLoader.js
import DataLoader from 'dataloader';
import { batchFetchUsers, batchFetchPosts } from './api';

// 用户数据加载器
export const userLoader = new DataLoader(
  async (userIds) => {
    const users = await batchFetchUsers(userIds);
    // 保证返回顺序与输入一致
    return userIds.map(id => 
      users.find(user => user.id === id) || new Error(`User ${id} not found`)
    );
  },
  {
    cache: true,
    maxBatchSize: 100,
    batchScheduleFn: callback => setTimeout(callback, 10), // 10ms 批量窗口
  }
);

// 帖子数据加载器
export const postLoader = new DataLoader(
  async (postIds) => {
    const posts = await batchFetchPosts(postIds);
    return postIds.map(id => 
      posts.filter(post => post.id === id)
    );
  },
  {
    cache: true,
    cacheKeyFn: (key) => `post:${key}`,
  }
);

// GraphQL-style 批量获取
export async function batchFetchUsers(ids) {
  const query = `
    query BatchFetchUsers($ids: [ID!]!) {
      users(ids: $ids) {
        id
        name
        avatar
        postsCount
        followersCount
      }
    }
  `;
  
  const response = await fetch(process.env.GRAPHQL_ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables: { ids } }),
  });
  
  const { data } = await response.json();
  return data.users;
}

缓存策略优化

多层缓存架构
flowchart TD
    A[用户请求] --> B[CDN/Edge Cache]
    B -->|Cache Miss| C[Load Balancer]
    C --> D[Server Instance]
    D --> E[Memory Cache L1]
    E -->|Cache Miss| F[Redis Cache L2]
    F -->|Cache Miss| G[Database]
    
    G --> H[数据返回]
    H --> F
    F --> E
    E --> D
    D --> I[SSR Rendering]
    I --> J[HTML Response]
    J --> C
    C --> B
    B --> K[用户获得响应]
    
    style B fill:#e3f2fd
    style E fill:#f3e5f5
    style F fill:#e8f5e8
    style I fill:#fff3e0
智能缓存实现
// cache/smartCache.js
import { Redis } from 'ioredis';
import { LRUCache } from 'lru-cache';

class SmartCache {
  constructor(options = {}) {
    this.redis = new Redis(process.env.REDIS_URL);
    this.memory = new LRUCache({
      max: options.memoryMax || 1000,
      ttl: options.memoryTTL || 60000, // 1 分钟
    });
    
    this.stats = {
      hits: 0,
      misses: 0,
      memoryHits: 0,
      redisHits: 0,
    };
  }
  
  async get(key, options = {}) {
    const { fallback, ttl = 300 } = options;
    
    // L1: 内存缓存
    const memoryValue = this.memory.get(key);
    if (memoryValue) {
      this.stats.hits++;
      this.stats.memoryHits++;
      return memoryValue;
    }
    
    // L2: Redis 缓存
    const redisValue = await this.redis.get(key);
    if (redisValue) {
      this.stats.hits++;
      this.stats.redisHits++;
      
      const parsed = JSON.parse(redisValue);
      // 回填 L1 缓存
      this.memory.set(key, parsed);
      return parsed;
    }
    
    // 缓存未命中
    this.stats.misses++;
    
    if (fallback) {
      const value = await fallback();
      await this.set(key, value, ttl);
      return value;
    }
    
    return null;
  }
  
  async set(key, value, ttl = 300) {
    // 同时设置两层缓存
    this.memory.set(key, value);
    await this.redis.setex(key, ttl, JSON.stringify(value));
  }
  
  // 智能失效
  async invalidatePattern(pattern) {
    const keys = await this.redis.keys(pattern);
    
    if (keys.length > 0) {
      // 批量删除 Redis
      await this.redis.del(...keys);
      
      // 清理内存缓存中匹配的键
      for (const [memKey] of this.memory.entries()) {
        if (this.matchPattern(memKey, pattern)) {
          this.memory.delete(memKey);
        }
      }
    }
  }
  
  // 缓存统计
  getStats() {
    const hitRate = this.stats.hits / (this.stats.hits + this.stats.misses) * 100;
    return {
      ...this.stats,
      hitRate: hitRate.toFixed(2) + '%',
      memorySize: this.memory.size,
    };
  }
  
  matchPattern(key, pattern) {
    return new RegExp(pattern.replace(/\*/g, '.*')).test(key);
  }
}

const cache = new SmartCache();
export default cache;

源码分割与加载优化

动态导入和代码分割
// next.config.js - 高级打包优化
module.exports = {
  webpack: (config, { isServer, dev, defaultLoaders }) => {
    if (!isServer && !dev) {
      // 客户端生产环境优化
      config.optimization.splitChunks = {
        chunks: 'all',
        minSize: 20000,
        maxSize: 244000,
        cacheGroups: {
          // 框架代码
          framework: {
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
            name: 'framework',
            chunks: 'all',
            priority: 40,
            enforce: true,
          },
          // 库代码
          lib: {
            test: /[\\/]node_modules[\\/]/,
            name: 'lib',
            chunks: 'all',
            priority: 30,
            minChunks: 2,
          },
          // 公共组件
          commons: {
            name: 'commons',
            minChunks: 2,
            priority: 20,
            reuseExistingChunk: true,
          },
          // 页面特定代码
          pages: {
            test: /[\\/]pages[\\/]/,
            name: 'pages',
            priority: 10,
            minChunks: 2,
          },
        },
      };
    }
    
    return config;
  },
  
  // 实验性特性
  experimental: {
    modern: true, // 现代浏览器优化
    optimizeServerReact: true, // 服务端 React 优化
    serverComponents: true, // React Server Components
  },
};
动态组件加载
// components/DynamicLoader.js
import dynamic from 'next/dynamic';
import { Suspense } from 'react';

// 基础动态加载
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <div>加载中...</div>,
  ssr: false, // 禁用 SSR
});

// 条件加载
const AdminPanel = dynamic(
  () => import('./AdminPanel'),
  {
    loading: () => <div>加载管理面板...</div>,
    ssr: false,
  }
);

// 多组件批量加载
const DynamicComponents = {
  Chart: dynamic(() => import('./Chart'), {
    loading: () => <div>加载图表...</div>,
  }),
  DataTable: dynamic(() => import('./DataTable'), {
    loading: () => <div>加载表格...</div>,
  }),
  Map: dynamic(() => import('./Map'), {
    loading: () => <div>加载地图...</div>,
    ssr: false, // 地图组件通常不需要 SSR
  }),
};

// 智能组件加载器
export function SmartComponentLoader({ componentName, condition, fallback, ...props }) {
  if (!condition) {
    return fallback || null;
  }
  
  const Component = DynamicComponents[componentName];
  
  if (!Component) {
    console.warn(`Component ${componentName} not found`);
    return fallback || <div>组件不存在</div>;
  }
  
  return (
    <Suspense fallback={fallback || <div>加载中...</div>}>
      <Component {...props} />
    </Suspense>
  );
}

// 使用示例
export default function Dashboard({ user, showChart, showMap }) {
  return (
    <div className="dashboard">
      <h1>仪表盘</h1>
      
      {/* 条件性加载组件 */}
      <SmartComponentLoader
        componentName="Chart"
        condition={showChart}
        data={user.chartData}
        fallback={<div className="chart-placeholder">图表占位符</div>}
      />
      
      <SmartComponentLoader
        componentName="Map"
        condition={showMap}
        locations={user.locations}
      />
      
      {/* 管理员功能 */}
      {user.isAdmin && (
        <SmartComponentLoader
          componentName="AdminPanel"
          condition={user.isAdmin}
          userId={user.id}
        />
      )}
    </div>
  );
}

性能监控与调优

性能指标收集
// utils/performanceMonitor.js
class SSRPerformanceMonitor {
  constructor() {
    this.metrics = new Map();
    this.thresholds = {
      ttfb: 800, // Time to First Byte
      renderTime: 2000, // SSR 渲染时间
      memoryUsage: 1024 * 1024 * 100, // 100MB
    };
  }
  
  startTiming(label) {
    this.metrics.set(label, {
      startTime: process.hrtime.bigint(),
      startMemory: process.memoryUsage(),
    });
  }
  
  endTiming(label) {
    const metric = this.metrics.get(label);
    if (!metric) return null;
    
    const endTime = process.hrtime.bigint();
    const endMemory = process.memoryUsage();
    
    const result = {
      label,
      duration: Number(endTime - metric.startTime) / 1000000, // 转换为毫秒
      memoryDelta: endMemory.heapUsed - metric.startMemory.heapUsed,
      timestamp: new Date().toISOString(),
    };
    
    this.metrics.delete(label);
    this.checkThresholds(result);
    
    return result;
  }
  
  checkThresholds(result) {
    const { label, duration, memoryDelta } = result;
    
    if (label.includes('render') && duration > this.thresholds.renderTime) {
      console.warn(`渲染耗时警告: ${label} 耗时 ${duration}ms`);
    }
    
    if (Math.abs(memoryDelta) > this.thresholds.memoryUsage) {
      console.warn(`内存使用警告: ${label} 内存变化 ${memoryDelta / 1024 / 1024}MB`);
    }
  }
  
  // 收集系统指标
  getSystemMetrics() {
    const memUsage = process.memoryUsage();
    const cpuUsage = process.cpuUsage();
    
    return {
      memory: {
        heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
        heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
        external: Math.round(memUsage.external / 1024 / 1024),
        rss: Math.round(memUsage.rss / 1024 / 1024),
      },
      cpu: {
        user: cpuUsage.user / 1000, // 转换为毫秒
        system: cpuUsage.system / 1000,
      },
      uptime: process.uptime(),
    };
  }
}

const monitor = new SSRPerformanceMonitor();
export default monitor;
Express 中间件集成
// middleware/performanceMiddleware.js
import monitor from '../utils/performanceMonitor';
import { Analytics } from '@segment/analytics-node';

const analytics = new Analytics({
  writeKey: process.env.SEGMENT_WRITE_KEY,
});

export function performanceMiddleware() {
  return (req, res, next) => {
    const requestId = req.headers['x-request-id'] || Math.random().toString(36);
    const startTime = Date.now();
    
    // 开始监控
    monitor.startTiming(`request:${requestId}`);
    
    // 拦截响应
    const originalSend = res.send;
    res.send = function(body) {
      const metrics = monitor.endTiming(`request:${requestId}`);
      const ttfb = Date.now() - startTime;
      
      // 记录性能数据
      const performanceData = {
        requestId,
        url: req.url,
        method: req.method,
        userAgent: req.headers['user-agent'],
        ttfb,
        renderTime: metrics?.duration || 0,
        contentLength: body.length,
        statusCode: res.statusCode,
        ...monitor.getSystemMetrics(),
      };
      
      // 发送到分析平台
      analytics.track({
        userId: req.user?.id || 'anonymous',
        event: 'SSR Performance',
        properties: performanceData,
      });
      
      // 设置响应头
      res.setHeader('X-Response-Time', `${ttfb}ms`);
      res.setHeader('X-Render-Time', `${metrics?.duration || 0}ms`);
      
      return originalSend.call(this, body);
    };
    
    next();
  };
}

最佳实践与总结

SSR 开发最佳实践

1. 渲染策略选择
flowchart LR
    A[页面类型分析] --> B{内容特性}
    
    B -->|静态内容| C[SSG]
    B -->|个性化内容| D[SSR]
    B -->|半静态内容| E[ISR]
    B -->|高交互性| F[CSR]
    
    C --> G[博客文章\n产品介绍\n营销页面]
    D --> H[用户仪表盘\n购物车\n个人资料]
    E --> I[产品列表\n新闻列表\n评论区]
    F --> J[在线编辑器\n游戏界面\n实时聊天]
    
    style C fill:#e8f5e8
    style D fill:#f3e5f5
    style E fill:#e3f2fd
    style F fill:#fff3e0
2. 性能优化检查清单
// utils/ssrOptimizationChecklist.js
export const SSROptimizationChecklist = {
  // 数据获取优化
  dataFetching: {
    parallelRequests: "✅ 使用 Promise.all 并行获取数据",
    dataLoaderPattern: "✅ 实现 DataLoader 批量处理",
    caching: "✅ 多层缓存策略",
    errorHandling: "✅ 优雅的错误处理和降级",
  },
  
  // 渲染优化
  rendering: {
    componentSplitting: "✅ 合理的组件拆分",
    lazyLoading: "✅ 非关键组件懒加载",
    memoization: "✅ 使用 React.memo 和 useMemo",
    streaming: "✅ 支持流式渲染",
  },
  
  // 资源优化
  assets: {
    codeSplitting: "✅ 代码分割和按需加载",
    imageOptimization: "✅ 图片优化和懒加载",
    cssOptimization: "✅ 关键 CSS 内联",
    fontOptimization: "✅ 字体预加载和优化",
  },
  
  // 缓存策略
  caching: {
    httpCaching: "✅ 合适的 HTTP 缓存头",
    edgeCaching: "✅ CDN/Edge 缓存配置",
    applicationCaching: "✅ 应用级缓存",
    browserCaching: "✅ 浏览器缓存优化",
  },
  
  // 监控和调试
  monitoring: {
    performanceMetrics: "✅ 性能指标收集",
    errorTracking: "✅ 错误监控和上报",
    realUserMonitoring: "✅ 真实用户监控",
    loadTesting: "✅ 负载测试验证",
  },
};

// 自动化检查函数
export function checkSSROptimization(app) {
  const issues = [];
  
  // 检查是否实现了关键优化
  if (!app.config.experimental?.optimizeServerReact) {
    issues.push("建议启用 optimizeServerReact");
  }
  
  if (!app.config.webpack?.optimization?.splitChunks) {
    issues.push("未配置代码分割");
  }
  
  if (!app.middleware?.includes('compression')) {
    issues.push("未启用 gzip/brotli 压缩");
  }
  
  return {
    score: Math.max(0, 100 - issues.length * 10),
    issues,
    recommendations: generateRecommendations(issues),
  };
}

function generateRecommendations(issues) {
  const recommendations = {
    "未配置代码分割": "配置 webpack splitChunks 以减少 bundle 大小",
    "未启用 gzip/brotli 压缩": "启用 compression 中间件以减少传输大小",
    "建议启用 optimizeServerReact": "在 next.config.js 中启用 experimental.optimizeServerReact",
  };
  
  return issues.map(issue => recommendations[issue] || "请查阅文档获取优化建议");
}
3. 错误处理和监控
// utils/ssrErrorHandler.js
export class SSRErrorHandler {
  constructor(options = {}) {
    this.isDevelopment = process.env.NODE_ENV === 'development';
    this.sentryDsn = options.sentryDsn;
    this.fallbackComponent = options.fallbackComponent;
    this.onError = options.onError;
  }
  
  // 全局错误处理
  handleRenderError(error, context) {
    const errorInfo = {
      message: error.message,
      stack: this.isDevelopment ? error.stack : undefined,
      url: context.req?.url,
      userAgent: context.req?.headers?.['user-agent'],
      timestamp: new Date().toISOString(),
      pid: process.pid,
    };
    
    // 记录错误
    console.error('SSR Render Error:', errorInfo);
    
    // 发送到监控服务
    if (this.sentryDsn) {
      this.reportToSentry(error, context);
    }
    
    // 自定义错误处理
    if (this.onError) {
      this.onError(error, context);
    }
    
    // 返回降级内容
    return this.getFallbackResponse(error, context);
  }
  
  getFallbackResponse(error, context) {
    // 根据错误类型返回不同的降级方案
    if (error.name === 'TimeoutError') {
      return {
        props: {
          error: 'timeout',
          fallbackData: this.getCachedData(context.req.url),
        },
      };
    }
    
    if (error.status === 404) {
      return { notFound: true };
    }
    
    // 通用降级
    return {
      props: {
        error: 'general',
        message: this.isDevelopment ? error.message : '服务暂时不可用',
      },
    };
  }
  
  reportToSentry(error, context) {
    // Sentry 错误上报
    if (typeof window === 'undefined') {
      // 服务端错误上报
      import('@sentry/node').then(Sentry => {
        Sentry.captureException(error, {
          tags: {
            component: 'ssr',
            url: context.req?.url,
          },
          extra: {
            userAgent: context.req?.headers?.['user-agent'],
            referer: context.req?.headers?.referer,
          },
        });
      });
    }
  }
  
  getCachedData(url) {
    // 尝试获取缓存数据作为降级方案
    try {
      return cache.get(`fallback:${url}`) || null;
    } catch {
      return null;
    }
  }
}

// 使用示例
export const errorHandler = new SSRErrorHandler({
  sentryDsn: process.env.SENTRY_DSN,
  onError: (error, context) => {
    // 自定义错误处理逻辑
    console.log('Custom error handling:', error.message);
  },
});
4. 开发和调试工具
// utils/ssrDebugger.js
export class SSRDebugger {
  constructor() {
    this.isDevelopment = process.env.NODE_ENV === 'development';
    this.debugMode = process.env.SSR_DEBUG === 'true';
  }
  
  // 性能分析
  analyzeSSRPerformance(metrics) {
    if (!this.debugMode) return;
    
    const analysis = {
      renderTime: metrics.renderTime,
      dataFetchTime: metrics.dataFetchTime,
      hydrationTime: metrics.hydrationTime,
      recommendations: [],
    };
    
    // 渲染时间分析
    if (metrics.renderTime > 2000) {
      analysis.recommendations.push("渲染时间过长,考虑组件优化或数据预加载");
    }
    
    // 数据获取分析
    if (metrics.dataFetchTime > 1000) {
      analysis.recommendations.push("数据获取耗时过长,考虑并行请求或缓存优化");
    }
    
    // Hydration 分析
    if (metrics.hydrationTime > 1000) {
      analysis.recommendations.push("Hydration 时间过长,检查客户端代码分割");
    }
    
    console.table(analysis);
    return analysis;
  }
  
  // 组件渲染分析
  analyzeComponentRender(componentName, renderTime) {
    if (!this.debugMode) return;
    
    console.log(`🔍 Component Render Analysis:`, {
      component: componentName,
      renderTime: `${renderTime}ms`,
      status: renderTime > 100 ? '⚠️ Slow' : '✅ Fast',
    });
  }
  
  // 数据获取分析
  analyzeDataFetching(endpoint, duration, cacheHit) {
    if (!this.debugMode) return;
    
    console.log(`📊 Data Fetching Analysis:`, {
      endpoint,
      duration: `${duration}ms`,
      cacheHit: cacheHit ? '✅ Hit' : '❌ Miss',
      performance: duration > 500 ? '⚠️ Slow' : '✅ Fast',
    });
  }
  
  // Bundle 分析
  analyzeBundleSize(bundles) {
    if (!this.debugMode) return;
    
    const analysis = bundles.map(bundle => ({
      name: bundle.name,
      size: `${(bundle.size / 1024).toFixed(2)}KB`,
      gzipped: `${(bundle.gzipped / 1024).toFixed(2)}KB`,
      recommendation: bundle.size > 250000 ? '考虑进一步拆分' : '大小合适',
    }));
    
    console.table(analysis);
  }
}

export const ssrDebugger = new SSRDebugger();

总结

通过本文的深入探讨,我们全面了解了 React SSR 的优势、实现方案和优化策略:

核心收益
  1. 首屏性能提升 - 显著减少 FCP 和 LCP 时间
  2. SEO 友好 - 搜索引擎可直接索引内容
  3. 更好的用户体验 - 即使 JavaScript 失败也能展示内容
  4. 社交媒体分享优化 - 动态生成 Open Graph 数据
技术要点
  1. 渲染策略选择 - 根据内容特性选择 SSR/SSG/ISR
  2. 性能优化 - 数据获取并行化、缓存策略、代码分割
  3. 错误处理 - 优雅降级、监控告警
  4. 开发体验 - 调试工具、性能分析
最佳实践
  1. 渐进式采用 - 从关键页面开始,逐步扩展
  2. 性能监控 - 持续监控关键指标,及时优化
  3. 缓存策略 - 多层缓存,智能失效
  4. 错误容错 - 完善的降级机制

SSR 不是银弹,需要根据具体场景权衡成本和收益。正确实施 SSR 可以显著提升应用的性能和用户体验,但也需要投入相应的开发和维护成本。通过本文提供的实践方案和优化技巧,可以帮助团队更好地实施和优化 SSR 应用。