React服务端渲染之ServerAPI

258 阅读9分钟

React 服务端渲染之 Server API 深度解析

目录

React DOM Server 完整 API 解析

所有 renderToXX 方法完整覆盖

React DOM Server 提供了多种渲染方法,每种都有其特定的使用场景和性能特点:

API 方法总览
方法环境输出类型Hydration支持流式渲染React 18特性
renderToStringNode.jsString有限支持
renderToStaticMarkupNode.jsString
renderToNodeStreamNode.jsStream有限支持
renderToStaticNodeStreamNode.jsStream
renderToPipeableStreamNode.jsStream
renderToReadableStreamWebStream
渲染流程架构图
graph TB
    A[React Element] --> B{选择渲染方法}
    
    B --> C[renderToString]
    B --> D[renderToStaticMarkup]
    B --> E[renderToNodeStream]
    B --> F[renderToStaticNodeStream]
    B --> G[renderToPipeableStream]
    B --> H[renderToReadableStream]
    
    C --> C1[同步渲染HTML字符串]
    C --> C2[包含data-reactroot属性]
    C --> C3[支持客户端水合]
    
    D --> D1[纯静态HTML字符串]
    D --> D2[不包含React属性]
    D --> D3[无法水合]
    
    E --> E1[Node.js可读流]
    E --> E2[支持背压处理]
    E --> E3[包含React属性]
    
    F --> F1[静态内容流]
    F --> F2[更小的输出]
    F --> F3[无React属性]
    
    G --> G1[支持Suspense]
    G --> G2[选择性水合]
    G --> G3[并发特性]
    
    H --> H1[Web流标准]
    H --> H2[边缘计算友好]
    H --> H3[现代浏览器API]
    
    style G fill:#e1f5fe
    style H fill:#c8e6c9

所有 renderToXX 方法详解

1. renderToString

用途:将React元素渲染为HTML字符串,是最传统的SSR方法。

特点

  • 同步渲染,阻塞式处理
  • 生成包含React属性的HTML
  • 支持客户端hydration
  • 不支持Suspense和并发特性
import { renderToString } from 'react-dom/server';

// 基础使用示例
function BasicApp({ title, content }) {
  return (
    <html>
      <head>
        <title>{title}</title>
      </head>
      <body>
        <div id="root">
          <h1>{title}</h1>
          <p>{content}</p>
        </div>
      </body>
    </html>
  );
}

// 完整的renderToString实现
class RenderToStringService {
  constructor() {
    this.renderCache = new Map();
    this.renderStats = {
      totalRenders: 0,
      averageTime: 0,
      errors: 0
    };
  }
  
  render(element, options = {}) {
    const startTime = Date.now();
    const cacheKey = options.cacheKey;
    
    try {
      // 检查缓存
      if (cacheKey && this.renderCache.has(cacheKey)) {
        const cached = this.renderCache.get(cacheKey);
        if (Date.now() - cached.timestamp < (options.cacheTTL || 300000)) {
          return {
            html: cached.html,
            cached: true,
            renderTime: 0
          };
        }
      }
      
      // 执行渲染
      const html = renderToString(element);
      const renderTime = Date.now() - startTime;
      
      // 更新统计
      this.updateStats(renderTime, false);
      
      // 缓存结果
      if (cacheKey) {
        this.renderCache.set(cacheKey, {
          html,
          timestamp: Date.now()
        });
      }
      
      // 返回结果
      return {
        html,
        cached: false,
        renderTime,
        size: Buffer.byteLength(html, 'utf8'),
        hasReactAttributes: html.includes('data-react')
      };
      
    } catch (error) {
      this.updateStats(Date.now() - startTime, true);
      console.error('renderToString failed:', error);
      
      // 错误降级
      return {
        html: this.getErrorFallback(error),
        cached: false,
        renderTime: Date.now() - startTime,
        error: error.message
      };
    }
  }
  
  updateStats(renderTime, isError) {
    this.renderStats.totalRenders++;
    
    if (isError) {
      this.renderStats.errors++;
    } else {
      const prevAvg = this.renderStats.averageTime;
      const count = this.renderStats.totalRenders - this.renderStats.errors;
      this.renderStats.averageTime = (prevAvg * (count - 1) + renderTime) / count;
    }
  }
  
  getErrorFallback(error) {
    return `
      <!DOCTYPE html>
      <html>
        <head>
          <title>渲染错误</title>
        </head>
        <body>
          <div id="root">
            <h1>页面渲染失败</h1>
            <p>正在加载客户端版本...</p>
            <script>
              console.error('SSR Error:', ${JSON.stringify(error.message)});
            </script>
          </div>
        </body>
      </html>
    `;
  }
  
  getStats() {
    return {
      ...this.renderStats,
      cacheSize: this.renderCache.size,
      errorRate: this.renderStats.errors / this.renderStats.totalRenders
    };
  }
}

// 使用示例
const renderService = new RenderToStringService();

// Express.js 集成
app.get('/', (req, res) => {
  const appElement = <BasicApp title="首页" content="欢迎访问我们的网站" />;
  
  const result = renderService.render(appElement, {
    cacheKey: `home:${req.headers['accept-language']}`,
    cacheTTL: 600000 // 10分钟缓存
  });
  
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
  res.setHeader('X-Render-Time', result.renderTime);
  res.setHeader('X-Cached', result.cached);
  
  res.send(result.html);
});

2. renderToStaticMarkup

用途:渲染纯静态HTML,不包含React特有的DOM属性。

特点

  • 输出更干净的HTML
  • 文件体积更小
  • 不支持客户端hydration
  • 适用于静态页面生成
import { renderToStaticMarkup } from 'react-dom/server';

// 静态页面组件
function StaticPage({ title, content, lastModified }) {
  return (
    <>
      <h1>{title}</h1>
      <article dangerouslySetInnerHTML={{ __html: content }} />
      <footer>
        <p>最后更新:{new Date(lastModified).toLocaleDateString('zh-CN')}</p>
      </footer>
    </>
  );
}

// 静态站点生成器
class StaticSiteGenerator {
  constructor(options = {}) {
    this.options = {
      outputDir: './dist',
      templatePath: './templates/base.html',
      minify: true,
      ...options
    };
  }
  
  async generatePage(component, props, outputPath) {
    try {
      // 渲染组件为静态HTML
      const componentHTML = renderToStaticMarkup(component(props));
      
      // 读取HTML模板
      const template = await this.loadTemplate();
      
      // 构建完整页面
      const fullHTML = this.buildFullPage(template, componentHTML, props);
      
      // 压缩HTML(如果启用)
      const finalHTML = this.options.minify ? this.minifyHTML(fullHTML) : fullHTML;
      
      // 写入文件
      await this.writeFile(outputPath, finalHTML);
      
      return {
        success: true,
        outputPath,
        size: Buffer.byteLength(finalHTML, 'utf8'),
        compressed: this.options.minify
      };
      
    } catch (error) {
      console.error(`Failed to generate ${outputPath}:`, error);
      return {
        success: false,
        error: error.message,
        outputPath
      };
    }
  }
  
  async loadTemplate() {
    const fs = require('fs').promises;
    return await fs.readFile(this.options.templatePath, 'utf8');
  }
  
  buildFullPage(template, content, props) {
    return template
      .replace('{{TITLE}}', props.title || '')
      .replace('{{META_DESCRIPTION}}', props.description || '')
      .replace('{{CONTENT}}', content)
      .replace('{{CANONICAL_URL}}', props.canonicalUrl || '')
      .replace('{{LANG}}', props.lang || 'zh-CN');
  }
  
  minifyHTML(html) {
    // 简化的HTML压缩
    return html
      .replace(/\s+/g, ' ')
      .replace(/>\s+</g, '><')
      .replace(/^\s+|\s+$/g, '')
      .trim();
  }
  
  async writeFile(path, content) {
    const fs = require('fs').promises;
    const dir = require('path').dirname(path);
    
    // 确保目录存在
    await fs.mkdir(dir, { recursive: true });
    
    // 写入文件
    await fs.writeFile(path, content, 'utf8');
  }
  
  // 批量生成静态页面
  async generateSite(pages) {
    const results = [];
    
    for (const page of pages) {
      const result = await this.generatePage(
        page.component,
        page.props,
        `${this.options.outputDir}${page.path}/index.html`
      );
      
      results.push({
        ...result,
        path: page.path
      });
    }
    
    // 生成站点地图
    await this.generateSitemap(pages.filter((_, i) => results[i].success));
    
    return results;
  }
  
  async generateSitemap(pages) {
    const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map(page => `
  <url>
    <loc>${page.props.canonicalUrl || `https://example.com${page.path}`}</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>
`).join('')}
</urlset>`;
    
    await this.writeFile(`${this.options.outputDir}/sitemap.xml`, sitemap);
  }
}

// 使用示例
const generator = new StaticSiteGenerator({
  outputDir: './public',
  templatePath: './templates/page.html',
  minify: true
});

// 生成静态网站
const pages = [
  {
    path: '/',
    component: StaticPage,
    props: {
      title: '首页',
      content: '<p>欢迎访问我们的网站</p>',
      description: '这是网站首页',
      canonicalUrl: 'https://example.com/',
      lastModified: Date.now()
    }
  },
  {
    path: '/about',
    component: StaticPage,
    props: {
      title: '关于我们',
      content: '<p>我们是一家专业的技术公司</p>',
      description: '了解我们公司的发展历程',
      canonicalUrl: 'https://example.com/about',
      lastModified: Date.now()
    }
  }
];

generator.generateSite(pages).then(results => {
  console.log('静态站点生成完成:');
  results.forEach(result => {
    if (result.success) {
      console.log(`✓ ${result.path} (${result.size} bytes)`);
    } else {
      console.log(`✗ ${result.path} - ${result.error}`);
    }
  });
});

3. renderToNodeStream

用途:将React元素渲染为Node.js可读流,支持流式传输。

特点

  • 支持背压处理
  • 更好的内存使用
  • 包含React hydration属性
  • 逐步发送HTML内容
import { renderToNodeStream } from 'react-dom/server';

// 流式渲染服务
class StreamingRenderService {
  constructor() {
    this.activeStreams = new Set();
    this.streamStats = {
      totalStreams: 0,
      activeCount: 0,
      bytesStreamed: 0,
      errors: 0
    };
  }
  
  createStream(element, options = {}) {
    const {
      onError = this.defaultErrorHandler,
      onComplete = this.defaultCompleteHandler,
      timeout = 30000
    } = options;
    
    try {
      // 创建渲染流
      const renderStream = renderToNodeStream(element);
      
      // 创建增强的流包装器
      const enhancedStream = this.createEnhancedStream(renderStream, {
        onError,
        onComplete,
        timeout
      });
      
      // 跟踪流状态
      this.trackStream(enhancedStream);
      
      return enhancedStream;
      
    } catch (error) {
      console.error('Failed to create render stream:', error);
      onError(error);
      return null;
    }
  }
  
  createEnhancedStream(originalStream, options) {
    const { Transform } = require('stream');
    let bytesTransferred = 0;
    let chunksCount = 0;
    const startTime = Date.now();
    
    const enhancedStream = new Transform({
      transform(chunk, encoding, callback) {
        bytesTransferred += chunk.length;
        chunksCount++;
        
        // 更新全局统计
        this.streamStats.bytesStreamed += chunk.length;
        
        // 可以在这里添加流处理逻辑
        // 例如:添加性能标记、内容修改等
        
        callback(null, chunk);
      }.bind(this),
      
      flush(callback) {
        const duration = Date.now() - startTime;
        
        options.onComplete({
          bytesTransferred,
          chunksCount,
          duration,
          averageChunkSize: bytesTransferred / chunksCount
        });
        
        callback();
      }
    });
    
    // 设置超时
    const timeoutId = setTimeout(() => {
      enhancedStream.destroy(new Error('Stream timeout'));
    }, options.timeout);
    
    // 管道连接
    originalStream.pipe(enhancedStream);
    
    // 错误处理
    originalStream.on('error', (error) => {
      clearTimeout(timeoutId);
      options.onError(error);
    });
    
    enhancedStream.on('end', () => {
      clearTimeout(timeoutId);
    });
    
    return enhancedStream;
  }
  
  trackStream(stream) {
    this.activeStreams.add(stream);
    this.streamStats.totalStreams++;
    this.streamStats.activeCount++;
    
    stream.on('end', () => {
      this.activeStreams.delete(stream);
      this.streamStats.activeCount--;
    });
    
    stream.on('error', () => {
      this.activeStreams.delete(stream);
      this.streamStats.activeCount--;
      this.streamStats.errors++;
    });
  }
  
  defaultErrorHandler(error) {
    console.error('Stream error:', error);
  }
  
  defaultCompleteHandler(stats) {
    console.log('Stream completed:', stats);
  }
  
  getStats() {
    return {
      ...this.streamStats,
      averageBytesPerStream: this.streamStats.bytesStreamed / this.streamStats.totalStreams
    };
  }
  
  // 优雅关闭所有活跃流
  async closeAllStreams() {
    const promises = Array.from(this.activeStreams).map(stream => {
      return new Promise((resolve) => {
        stream.on('end', resolve);
        stream.on('error', resolve);
        stream.destroy();
      });
    });
    
    await Promise.all(promises);
    console.log('All streams closed');
  }
}

// 大型应用组件
function LargeApp({ user, posts, comments }) {
  return (
    <html>
      <head>
        <title>用户主页 - {user.name}</title>
        <meta name="description" content={`${user.name}的个人主页`} />
      </head>
      <body>
        <div id="root">
          <header>
            <h1>欢迎,{user.name}!</h1>
            <nav>
              <a href="/">首页</a>
              <a href="/profile">个人资料</a>
              <a href="/settings">设置</a>
            </nav>
          </header>
          
          <main>
            <section className="posts">
              <h2>最新动态</h2>
              {posts.map(post => (
                <article key={post.id}>
                  <h3>{post.title}</h3>
                  <p>{post.content}</p>
                  <div className="post-meta">
                    <span>发布时间:{new Date(post.createdAt).toLocaleDateString('zh-CN')}</span>
                    <span>阅读量:{post.views}</span>
                  </div>
                </article>
              ))}
            </section>
            
            <section className="comments">
              <h2>最新评论</h2>
              {comments.map(comment => (
                <div key={comment.id} className="comment">
                  <strong>{comment.author}</strong>
                  <p>{comment.content}</p>
                  <time>{new Date(comment.createdAt).toLocaleDateString('zh-CN')}</time>
                </div>
              ))}
            </section>
          </main>
          
          <footer>
            <p>© 2024 我的网站. 保留所有权利.</p>
          </footer>
        </div>
      </body>
    </html>
  );
}

// Express.js 集成示例
const streamingService = new StreamingRenderService();

app.get('/user/:id', async (req, res) => {
  try {
    // 获取用户数据
    const [user, posts, comments] = await Promise.all([
      getUserById(req.params.id),
      getUserPosts(req.params.id, { limit: 10 }),
      getUserComments(req.params.id, { limit: 5 })
    ]);
    
    if (!user) {
      return res.status(404).send('用户不存在');
    }
    
    // 创建应用元素
    const appElement = <LargeApp user={user} posts={posts} comments={comments} />;
    
    // 创建流式渲染
    const stream = streamingService.createStream(appElement, {
      timeout: 15000,
      onError: (error) => {
        console.error(`Streaming error for user ${req.params.id}:`, error);
        if (!res.headersSent) {
          res.status(500).send('渲染失败');
        }
      },
      onComplete: (stats) => {
        console.log(`Streaming completed for user ${req.params.id}:`, stats);
      }
    });
    
    if (!stream) {
      return res.status(500).send('无法创建渲染流');
    }
    
    // 设置响应头
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.setHeader('Transfer-Encoding', 'chunked');
    
    // 管道连接到响应
    stream.pipe(res);
    
    // 处理客户端断开连接
    req.on('close', () => {
      stream.destroy();
    });
    
  } catch (error) {
    console.error('Request handling error:', error);
    res.status(500).send('服务器内部错误');
  }
});

// 健康检查端点
app.get('/health/streaming', (req, res) => {
  const stats = streamingService.getStats();
  res.json({
    status: 'healthy',
    streaming: stats,
    timestamp: new Date().toISOString()
  });
});

// 优雅关闭
process.on('SIGTERM', async () => {
  console.log('Received SIGTERM, closing streams...');
  await streamingService.closeAllStreams();
  process.exit(0);
});

4. renderToStaticNodeStream

用途:生成不包含React属性的静态HTML流。

特点

  • 纯静态内容流
  • 更小的输出体积
  • 不支持hydration
  • 适用于静态内容生成
import { renderToStaticNodeStream } from 'react-dom/server';

// 静态内容流生成器
class StaticStreamGenerator {
  constructor(options = {}) {
    this.options = {
      compressionLevel: 6,
      enableGzip: true,
      bufferSize: 64 * 1024, // 64KB
      ...options
    };
    
    this.generationStats = {
      totalGenerated: 0,
      totalBytes: 0,
      averageSize: 0,
      compressionRatio: 0
    };
  }
  
  generateStaticStream(element, options = {}) {
    const {
      enableCompression = this.options.enableGzip,
      bufferSize = this.options.bufferSize
    } = options;
    
    try {
      // 创建静态渲染流
      const staticStream = renderToStaticNodeStream(element);
      
      // 创建处理流水线
      const pipeline = this.createProcessingPipeline(staticStream, {
        enableCompression,
        bufferSize
      });
      
      return pipeline;
      
    } catch (error) {
      console.error('Failed to create static stream:', error);
      throw error;
    }
  }
  
  createProcessingPipeline(sourceStream, options) {
    const { Transform, PassThrough } = require('stream');
    const zlib = require('zlib');
    
    let originalSize = 0;
    let compressedSize = 0;
    const startTime = Date.now();
    
    // 创建统计流
    const statsStream = new Transform({
      transform(chunk, encoding, callback) {
        originalSize += chunk.length;
        callback(null, chunk);
      }
    });
    
    // 创建缓冲流
    const bufferStream = new Transform({
      highWaterMark: options.bufferSize,
      transform(chunk, encoding, callback) {
        callback(null, chunk);
      }
    });
    
    let pipeline = sourceStream.pipe(statsStream).pipe(bufferStream);
    
    // 可选的压缩流
    if (options.enableCompression) {
      const gzipStream = zlib.createGzip({
        level: this.options.compressionLevel
      });
      
      // 跟踪压缩后大小
      const compressStatsStream = new Transform({
        transform(chunk, encoding, callback) {
          compressedSize += chunk.length;
          callback(null, chunk);
        }
      });
      
      pipeline = pipeline.pipe(gzipStream).pipe(compressStatsStream);
    }
    
    // 完成时更新统计
    pipeline.on('end', () => {
      this.updateStats({
        originalSize,
        compressedSize: options.enableCompression ? compressedSize : originalSize,
        processingTime: Date.now() - startTime,
        compressed: options.enableCompression
      });
    });
    
    return pipeline;
  }
  
  updateStats(streamStats) {
    this.generationStats.totalGenerated++;
    this.generationStats.totalBytes += streamStats.originalSize;
    this.generationStats.averageSize = 
      this.generationStats.totalBytes / this.generationStats.totalGenerated;
    
    if (streamStats.compressed) {
      const ratio = streamStats.compressedSize / streamStats.originalSize;
      this.generationStats.compressionRatio = 
        (this.generationStats.compressionRatio + ratio) / 2;
    }
  }
  
  // 生成静态RSS源
  generateRSSFeed(posts, siteInfo) {
    const RSSComponent = ({ posts, siteInfo }) => (
      <rss version="2.0">
        <channel>
          <title>{siteInfo.title}</title>
          <description>{siteInfo.description}</description>
          <link>{siteInfo.url}</link>
          <language>zh-CN</language>
          <lastBuildDate>{new Date().toUTCString()}</lastBuildDate>
          
          {posts.map(post => (
            <item key={post.id}>
              <title>{post.title}</title>
              <description>{post.summary}</description>
              <link>{`${siteInfo.url}/posts/${post.slug}`}</link>
              <guid>{post.id}</guid>
              <pubDate>{new Date(post.publishedAt).toUTCString()}</pubDate>
              <author>{post.author.email} ({post.author.name})</author>
            </item>
          ))}
        </channel>
      </rss>
    );
    
    return this.generateStaticStream(
      <RSSComponent posts={posts} siteInfo={siteInfo} />,
      { enableCompression: false } // RSS通常不压缩
    );
  }
  
  // 生成站点地图
  generateSitemap(urls) {
    const SitemapComponent = ({ urls }) => (
      <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
        {urls.map((url, index) => (
          <url key={index}>
            <loc>{url.loc}</loc>
            <lastmod>{url.lastmod}</lastmod>
            <changefreq>{url.changefreq || 'weekly'}</changefreq>
            <priority>{url.priority || '0.5'}</priority>
          </url>
        ))}
      </urlset>
    );
    
    return this.generateStaticStream(
      <SitemapComponent urls={urls} />,
      { enableCompression: true }
    );
  }
  
  getStats() {
    return this.generationStats;
  }
}

// 博客网站静态内容生成
function BlogPost({ post, relatedPosts, comments }) {
  return (
    <article className="blog-post">
      <header>
        <h1>{post.title}</h1>
        <div className="post-meta">
          <time dateTime={post.publishedAt}>
            {new Date(post.publishedAt).toLocaleDateString('zh-CN', {
              year: 'numeric',
              month: 'long',
              day: 'numeric'
            })}
          </time>
          <span className="author">作者:{post.author.name}</span>
          <span className="reading-time">阅读时长:{post.readingTime}分钟</span>
        </div>
      </header>
      
      <div className="post-content" dangerouslySetInnerHTML={{ __html: post.content }} />
      
      <section className="post-tags">
        <h3>标签</h3>
        {post.tags.map(tag => (
          <span key={tag.id} className="tag">#{tag.name}</span>
        ))}
      </section>
      
      <section className="related-posts">
        <h3>相关文章</h3>
        <ul>
          {relatedPosts.map(relatedPost => (
            <li key={relatedPost.id}>
              <a href={`/posts/${relatedPost.slug}`}>{relatedPost.title}</a>
              <time>{new Date(relatedPost.publishedAt).toLocaleDateString('zh-CN')}</time>
            </li>
          ))}
        </ul>
      </section>
      
      <section className="comments">
        <h3>评论 ({comments.length})</h3>
        {comments.map(comment => (
          <div key={comment.id} className="comment">
            <div className="comment-header">
              <strong>{comment.author}</strong>
              <time>{new Date(comment.createdAt).toLocaleDateString('zh-CN')}</time>
            </div>
            <div className="comment-content">{comment.content}</div>
          </div>
        ))}
      </section>
    </article>
  );
}

// 使用示例
const staticGenerator = new StaticStreamGenerator({
  enableGzip: true,
  compressionLevel: 9,
  bufferSize: 128 * 1024
});

// Express.js 路由集成
app.get('/posts/:slug', async (req, res) => {
  try {
    const [post, relatedPosts, comments] = await Promise.all([
      getPostBySlug(req.params.slug),
      getRelatedPosts(req.params.slug, 5),
      getPostComments(req.params.slug)
    ]);
    
    if (!post) {
      return res.status(404).send('文章不存在');
    }
    
    const blogElement = (
      <BlogPost 
        post={post} 
        relatedPosts={relatedPosts} 
        comments={comments} 
      />
    );
    
    const stream = staticGenerator.generateStaticStream(blogElement, {
      enableCompression: req.headers['accept-encoding']?.includes('gzip')
    });
    
    // 设置响应头
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    if (req.headers['accept-encoding']?.includes('gzip')) {
      res.setHeader('Content-Encoding', 'gzip');
    }
    res.setHeader('Cache-Control', 'public, max-age=3600'); // 1小时缓存
    
    // 流式响应
    stream.pipe(res);
    
    stream.on('error', (error) => {
      console.error('Static stream error:', error);
      if (!res.headersSent) {
        res.status(500).send('渲染失败');
      }
    });
    
  } catch (error) {
    console.error('Blog post rendering error:', error);
    res.status(500).send('服务器内部错误');
  }
});

// RSS源端点
app.get('/feed.xml', async (req, res) => {
  try {
    const posts = await getRecentPosts(20);
    const siteInfo = {
      title: '我的博客',
      description: '技术分享与思考',
      url: 'https://myblog.com'
    };
    
    const rssStream = staticGenerator.generateRSSFeed(posts, siteInfo);
    
    res.setHeader('Content-Type', 'application/rss+xml; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=1800'); // 30分钟缓存
    
    rssStream.pipe(res);
    
  } catch (error) {
    console.error('RSS generation error:', error);
    res.status(500).send('RSS生成失败');
  }
});

// 站点地图端点
app.get('/sitemap.xml', async (req, res) => {
  try {
    const urls = await generateSitemapUrls();
    const sitemapStream = staticGenerator.generateSitemap(urls);
    
    res.setHeader('Content-Type', 'application/xml; charset=utf-8');
    res.setHeader('Content-Encoding', 'gzip');
    res.setHeader('Cache-Control', 'public, max-age=86400'); // 24小时缓存
    
    sitemapStream.pipe(res);
    
  } catch (error) {
    console.error('Sitemap generation error:', error);
    res.status(500).send('站点地图生成失败');
  }
});

5. renderToPipeableStream (React 18)

用途:React 18的现代流式渲染API,支持Suspense和并发特性。

特点

  • 完整的React 18特性支持
  • 选择性水合
  • Suspense边界流式渲染
  • 更好的错误处理
import { renderToPipeableStream } from 'react-dom/server';
import { Suspense } from 'react';

// React 18 现代流式SSR系统
class ModernStreamingSSR {
  constructor(options = {}) {
    this.options = {
      bootstrapScripts: ['/js/client.js'],
      onShellReady: null,
      onShellError: null,
      onAllReady: null,
      onError: null,
      timeout: 10000,
      ...options
    };
    
    this.renderMetrics = {
      shellReadyTime: 0,
      totalRenderTime: 0,
      suspenseCount: 0,
      errorCount: 0,
      successCount: 0
    };
  }
  
  renderToStream(element, response, renderOptions = {}) {
    const startTime = Date.now();
    let shellReady = false;
    const metrics = { ...this.renderMetrics };
    
    const {
      onShellReady = this.options.onShellReady,
      onShellError = this.options.onShellError,
      onAllReady = this.options.onAllReady,
      onError = this.options.onError,
      bootstrapScripts = this.options.bootstrapScripts,
      timeout = this.options.timeout
    } = renderOptions;
    
    const { pipe, abort } = renderToPipeableStream(element, {
      bootstrapScripts,
      
      // Shell准备就绪 - 包含非Suspense内容
      onShellReady() {
        shellReady = true;
        metrics.shellReadyTime = Date.now() - startTime;
        
        console.log(`Shell ready in ${metrics.shellReadyTime}ms`);
        
        // 设置响应头
        response.statusCode = 200;
        response.setHeader('Content-Type', 'text/html; charset=utf-8');
        response.setHeader('Transfer-Encoding', 'chunked');
        response.setHeader('X-Shell-Time', metrics.shellReadyTime);
        
        // 开始流式传输
        pipe(response);
        
        // 调用自定义回调
        onShellReady?.();
      },
      
      // Shell渲染错误
      onShellError(error) {
        console.error('Shell render error:', error);
        metrics.errorCount++;
        
        response.statusCode = 500;
        response.setHeader('Content-Type', 'text/html; charset=utf-8');
        response.end(this.getErrorHTML(error, 'shell'));
        
        onShellError?.(error);
      },
      
      // 所有内容准备就绪 - 包括Suspense内容
      onAllReady() {
        metrics.totalRenderTime = Date.now() - startTime;
        metrics.successCount++;
        
        console.log(`All content ready in ${metrics.totalRenderTime}ms`);
        
        // 如果shell还未准备好,现在开始传输
        if (!shellReady) {
          response.statusCode = 200;
          response.setHeader('Content-Type', 'text/html; charset=utf-8');
          pipe(response);
        }
        
        // 更新响应头
        response.setHeader('X-Total-Time', metrics.totalRenderTime);
        
        onAllReady?.(metrics);
      },
      
      // 渲染过程中的错误
      onError(error, errorInfo) {
        console.error('Render error:', error, errorInfo);
        metrics.errorCount++;
        
        // 记录错误详情
        this.logRenderError(error, errorInfo);
        
        onError?.(error, errorInfo);
      }
    });
    
    // 设置超时处理
    const timeoutId = setTimeout(() => {
      console.warn(`Render timeout after ${timeout}ms, aborting...`);
      abort();
    }, timeout);
    
    // 清理资源
    response.on('close', () => {
      clearTimeout(timeoutId);
      console.log('Client disconnected, cleaning up render stream');
    });
    
    response.on('finish', () => {
      clearTimeout(timeoutId);
      this.updateGlobalMetrics(metrics);
    });
    
    return { abort, metrics };
  }
  
  logRenderError(error, errorInfo) {
    const errorLog = {
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo?.componentStack,
      timestamp: new Date().toISOString(),
      renderContext: 'streaming'
    };
    
    // 这里可以发送到错误监控服务
    console.error('Detailed render error:', errorLog);
  }
  
  getErrorHTML(error, context) {
    return `
      <!DOCTYPE html>
      <html lang="zh-CN">
        <head>
          <meta charset="utf-8">
          <title>页面加载失败</title>
          <style>
            body { font-family: system-ui, sans-serif; margin: 40px; }
            .error { background: #fee; border: 1px solid #fcc; padding: 20px; border-radius: 4px; }
            .error-title { color: #c33; margin: 0 0 10px 0; }
            .error-message { color: #666; }
            .retry-btn { 
              background: #007bff; color: white; border: none; 
              padding: 10px 20px; border-radius: 4px; cursor: pointer; margin-top: 15px;
            }
          </style>
        </head>
        <body>
          <div class="error">
            <h1 class="error-title">页面渲染失败</h1>
            <p class="error-message">
              渲染${context === 'shell' ? '基础内容' : '页面内容'}时发生错误,请稍后重试。
            </p>
            <button class="retry-btn" onclick="window.location.reload()">重新加载</button>
          </div>
          <script>
            console.error('SSR Error Context:', '${context}');
            console.error('Error Details:', ${JSON.stringify(error.message)});
            
            // 5秒后自动重新加载
            setTimeout(() => {
              window.location.reload();
            }, 5000);
          </script>
        </body>
      </html>
    `;
  }
  
  updateGlobalMetrics(metrics) {
    Object.keys(this.renderMetrics).forEach(key => {
      if (typeof this.renderMetrics[key] === 'number' && typeof metrics[key] === 'number') {
        this.renderMetrics[key] = (this.renderMetrics[key] + metrics[key]) / 2;
      }
    });
  }
  
  getStats() {
    return {
      ...this.renderMetrics,
      successRate: this.renderMetrics.successCount / 
        (this.renderMetrics.successCount + this.renderMetrics.errorCount)
    };
  }
}

// 支持Suspense的现代应用组件
function ModernApp({ userId, initialData }) {
  return (
    <html lang="zh-CN">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>现代SSR应用</title>
        <link rel="stylesheet" href="/css/app.css" />
      </head>
      <body>
        <div id="root">
          {/* 立即可见的内容 - Shell */}
          <Header />
          <Navigation />
          
          <main>
            {/* 快速加载的内容 */}
            <section className="hero">
              <h1>欢迎使用现代SSR应用</h1>
              <p>基于React 18的流式服务端渲染</p>
            </section>
            
            {/* 需要数据获取的内容 - Suspense边界 */}
            <Suspense fallback={<UserProfileSkeleton />}>
              <UserProfile userId={userId} />
            </Suspense>
            
            <Suspense fallback={<DashboardSkeleton />}>
              <UserDashboard userId={userId} />
            </Suspense>
            
            <Suspense fallback={<RecommendationsSkeleton />}>
              <PersonalizedRecommendations userId={userId} />
            </Suspense>
            
            {/* 低优先级内容 */}
            <Suspense fallback={<FooterSkeleton />}>
              <DynamicFooter />
            </Suspense>
          </main>
        </div>
        
        {/* 性能监控脚本 */}
        <script dangerouslySetInnerHTML={{
          __html: `
            window.__SSR_METRICS__ = {
              renderStart: ${Date.now()},
              shellReady: false,
              allReady: false
            };
            
            // 标记shell就绪
            if (document.readyState === 'loading') {
              document.addEventListener('DOMContentLoaded', () => {
                window.__SSR_METRICS__.shellReady = true;
                console.log('Shell hydration ready');
              });
            } else {
              window.__SSR_METRICS__.shellReady = true;
            }
          `
        }} />
      </body>
    </html>
  );
}

// 异步数据组件示例
async function UserProfile({ userId }) {
  // 模拟异步数据获取
  const user = await new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id: userId,
        name: '张三',
        avatar: '/avatars/zhangsan.jpg',
        bio: '全栈开发工程师,专注于React和Node.js开发',
        joinDate: '2020-01-15',
        followers: 1234,
        following: 567
      });
    }, Math.random() * 1000 + 500); // 0.5-1.5秒随机延迟
  });
  
  return (
    <section className="user-profile">
      <div className="profile-header">
        <img src={user.avatar} alt={user.name} className="avatar" />
        <div className="profile-info">
          <h2>{user.name}</h2>
          <p>{user.bio}</p>
          <div className="profile-stats">
            <span>{user.followers} 关注者</span>
            <span>{user.following} 正在关注</span>
            <span>加入于 {new Date(user.joinDate).toLocaleDateString('zh-CN')}</span>
          </div>
        </div>
      </div>
    </section>
  );
}

async function UserDashboard({ userId }) {
  const dashboardData = await new Promise(resolve => {
    setTimeout(() => {
      resolve({
        totalPosts: 42,
        totalLikes: 1337,
        totalComments: 89,
        recentActivity: [
          { type: 'post', title: '发布了新文章:React 18 新特性解析', time: '2小时前' },
          { type: 'like', title: '点赞了文章:JavaScript 性能优化', time: '5小时前' },
          { type: 'comment', title: '评论了文章:现代前端架构设计', time: '1天前' }
        ]
      });
    }, Math.random() * 800 + 300);
  });
  
  return (
    <section className="user-dashboard">
      <h3>个人统计</h3>
      <div className="stats-grid">
        <div className="stat-card">
          <h4>发布文章</h4>
          <span className="stat-number">{dashboardData.totalPosts}</span>
        </div>
        <div className="stat-card">
          <h4>获得点赞</h4>
          <span className="stat-number">{dashboardData.totalLikes}</span>
        </div>
        <div className="stat-card">
          <h4>收到评论</h4>
          <span className="stat-number">{dashboardData.totalComments}</span>
        </div>
      </div>
      
      <div className="recent-activity">
        <h4>最近活动</h4>
        <ul>
          {dashboardData.recentActivity.map((activity, index) => (
            <li key={index} className={`activity-${activity.type}`}>
              <span className="activity-title">{activity.title}</span>
              <span className="activity-time">{activity.time}</span>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}

// 骨架屏组件
function UserProfileSkeleton() {
  return (
    <section className="user-profile skeleton">
      <div className="profile-header">
        <div className="avatar skeleton-box"></div>
        <div className="profile-info">
          <div className="skeleton-text skeleton-title"></div>
          <div className="skeleton-text"></div>
          <div className="skeleton-text skeleton-half"></div>
        </div>
      </div>
    </section>
  );
}

function DashboardSkeleton() {
  return (
    <section className="user-dashboard skeleton">
      <div className="skeleton-text skeleton-title"></div>
      <div className="stats-grid">
        {[1, 2, 3].map(i => (
          <div key={i} className="stat-card skeleton-box"></div>
        ))}
      </div>
    </section>
  );
}

// Express.js 集成
const modernSSR = new ModernStreamingSSR({
  bootstrapScripts: ['/js/client.js', '/js/polyfills.js'],
  timeout: 15000
});

app.get('/app/:userId?', (req, res) => {
  const userId = req.params.userId || 'guest';
  
  const appElement = <ModernApp userId={userId} />;
  
  const { abort } = modernSSR.renderToStream(appElement, res, {
    onShellReady: () => {
      console.log(`Shell ready for user: ${userId}`);
    },
    onAllReady: (metrics) => {
      console.log(`Complete render for user: ${userId}`, metrics);
    },
    onError: (error, errorInfo) => {
      console.error(`Render error for user: ${userId}`, error);
      // 可以在这里发送错误监控
    }
  });
  
  // 请求取消处理
  req.on('close', () => {
    console.log(`Client disconnected for user: ${userId}`);
    abort();
  });
});

// 性能统计端点
app.get('/api/ssr-stats', (req, res) => {
  const stats = modernSSR.getStats();
  res.json({
    status: 'ok',
    ssr: stats,
    timestamp: new Date().toISOString()
  });
});

6. renderToReadableStream (Web Streams)

用途:为Web环境(如Cloudflare Workers、Deno)设计的流式渲染API。

特点

  • Web标准流API
  • 边缘计算友好
  • 现代浏览器原生支持
  • 更好的流控制
// 注意:这个API主要用于Web环境,如Cloudflare Workers
// 这里提供Node.js环境的模拟实现示例

import { renderToReadableStream } from 'react-dom/server';

// Web流式SSR服务(适用于边缘计算环境)
class WebStreamingSSR {
  constructor(options = {}) {
    this.options = {
      signal: null,
      onError: null,
      bootstrapScripts: [],
      ...options
    };
    
    this.streamMetrics = {
      totalStreams: 0,
      activeStreams: 0,
      averageSize: 0,
      errorRate: 0
    };
  }
  
  async renderToWebStream(element, options = {}) {
    const startTime = Date.now();
    let bytesGenerated = 0;
    
    const {
      signal = this.options.signal,
      onError = this.options.onError,
      bootstrapScripts = this.options.bootstrapScripts
    } = options;
    
    try {
      this.streamMetrics.totalStreams++;
      this.streamMetrics.activeStreams++;
      
      // 创建Web可读流
      const stream = await renderToReadableStream(element, {
        bootstrapScripts,
        signal,
        onError: (error, errorInfo) => {
          console.error('Web stream render error:', error);
          this.streamMetrics.errorRate = 
            (this.streamMetrics.errorRate + 1) / this.streamMetrics.totalStreams;
          onError?.(error, errorInfo);
        }
      });
      
      // 创建增强的流处理器
      const enhancedStream = this.createEnhancedWebStream(stream, {
        onData: (chunk) => {
          bytesGenerated += chunk.length;
        },
        onComplete: () => {
          const duration = Date.now() - startTime;
          this.updateStreamMetrics(bytesGenerated, duration);
          this.streamMetrics.activeStreams--;
        }
      });
      
      return enhancedStream;
      
    } catch (error) {
      this.streamMetrics.activeStreams--;
      console.error('Failed to create web stream:', error);
      throw error;
    }
  }
  
  createEnhancedWebStream(originalStream, callbacks) {
    const { onData, onComplete } = callbacks;
    
    // 创建转换流来监控数据
    const transformStream = new TransformStream({
      transform(chunk, controller) {
        onData?.(chunk);
        controller.enqueue(chunk);
      },
      flush() {
        onComplete?.();
      }
    });
    
    return originalStream.pipeThrough(transformStream);
  }
  
  updateStreamMetrics(bytes, duration) {
    const currentAvg = this.streamMetrics.averageSize;
    const count = this.streamMetrics.totalStreams;
    this.streamMetrics.averageSize = (currentAvg * (count - 1) + bytes) / count;
  }
  
  getMetrics() {
    return this.streamMetrics;
  }
}

// Cloudflare Workers 示例
const webSSR = new WebStreamingSSR();

// Workers 环境的应用组件
function WorkersApp({ url, userAgent, cf }) {
  return (
    <html lang="zh-CN">
      <head>
        <meta charSet="utf-8" />
        <title>边缘计算SSR应用</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </head>
      <body>
        <div id="root">
          <header>
            <h1>边缘计算渲染</h1>
            <p>此页面在离您最近的边缘节点渲染</p>
          </header>
          
          <main>
            <section className="request-info">
              <h2>请求信息</h2>
              <dl>
                <dt>访问URL:</dt>
                <dd>{url}</dd>
                
                <dt>用户代理:</dt>
                <dd>{userAgent}</dd>
                
                {cf && (
                  <>
                    <dt>数据中心:</dt>
                    <dd>{cf.colo}</dd>
                    
                    <dt>国家/地区:</dt>
                    <dd>{cf.country}</dd>
                    
                    <dt>时区:</dt>
                    <dd>{cf.timezone}</dd>
                  </>
                )}
              </dl>
            </section>
            
            <section className="edge-features">
              <h2>边缘计算特性</h2>
              <ul>
                <li>✅ 超低延迟渲染</li>
                <li>✅ 全球CDN分发</li>
                <li>✅ 自动扩缩容</li>
                <li>✅ 零冷启动</li>
              </ul>
            </section>
          </main>
          
          <footer>
            <p>渲染时间: {new Date().toISOString()}</p>
            <p>Powered by Cloudflare Workers + React SSR</p>
          </footer>
        </div>
      </body>
    </html>
  );
}

// Cloudflare Workers 处理器
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    // 路由处理
    if (url.pathname === '/') {
      try {
        const stream = await webSSR.renderToWebStream(
          <WorkersApp 
            url={request.url}
            userAgent={request.headers.get('User-Agent')}
            cf={request.cf}
          />,
          {
            // 处理取消信号
            signal: request.signal,
            onError: (error) => {
              console.error('Workers SSR error:', error);
            }
          }
        );
        
        return new Response(stream, {
          headers: {
            'Content-Type': 'text/html; charset=utf-8',
            'Cache-Control': 'public, max-age=300', // 5分钟缓存
            'X-Rendered-By': 'Cloudflare-Workers-SSR'
          }
        });
        
      } catch (error) {
        console.error('Workers render failed:', error);
        
        return new Response(
          `<h1>渲染失败</h1><p>错误信息: ${error.message}</p>`,
          {
            status: 500,
            headers: { 'Content-Type': 'text/html; charset=utf-8' }
          }
        );
      }
    }
    
    // API路由示例
    if (url.pathname === '/api/metrics') {
      const metrics = webSSR.getMetrics();
      return Response.json({
        metrics,
        worker: {
          colo: request.cf?.colo,
          country: request.cf?.country,
          timestamp: new Date().toISOString()
        }
      });
    }
    
    return new Response('Not Found', { status: 404 });
  }
};

// Deno 环境示例
if (typeof Deno !== 'undefined') {
  const server = Deno.serve({ port: 8000 }, async (request) => {
    const url = new URL(request.url);
    
    if (url.pathname === '/') {
      try {
        const stream = await webSSR.renderToWebStream(
          <WorkersApp 
            url={request.url}
            userAgent={request.headers.get('User-Agent')}
          />
        );
        
        return new Response(stream, {
          headers: {
            'Content-Type': 'text/html; charset=utf-8',
            'X-Rendered-By': 'Deno-SSR'
          }
        });
        
      } catch (error) {
        return new Response(`Error: ${error.message}`, { status: 500 });
      }
    }
    
    return new Response('Not Found', { status: 404 });
  });
  
  console.log('Deno SSR server running on http://localhost:8000');
}

// Node.js环境的Web Streams适配器
class NodeWebStreamAdapter {
  static async renderToNodeResponse(element, nodeResponse, options = {}) {
    try {
      // 在Node.js中模拟Web Streams
      const webStream = await renderToReadableStream(element, options);
      
      // 转换为Node.js流
      const nodeStream = this.webStreamToNodeStream(webStream);
      
      // 设置响应头
      nodeResponse.setHeader('Content-Type', 'text/html; charset=utf-8');
      nodeResponse.setHeader('X-Stream-Type', 'web-streams-adapter');
      
      // 管道到响应
      nodeStream.pipe(nodeResponse);
      
      return nodeStream;
      
    } catch (error) {
      console.error('Web stream adapter error:', error);
      nodeResponse.status(500).send('渲染失败');
      throw error;
    }
  }
  
  static webStreamToNodeStream(webStream) {
    const { Readable } = require('stream');
    const reader = webStream.getReader();
    
    return new Readable({
      async read() {
        try {
          const { done, value } = await reader.read();
          
          if (done) {
            this.push(null); // 结束流
          } else {
            this.push(value);
          }
        } catch (error) {
          this.destroy(error);
        }
      }
    });
  }
}

// Node.js Express 使用示例
if (typeof require !== 'undefined') {
  const express = require('express');
  const app = express();
  
  app.get('/web-streams', async (req, res) => {
    const element = <WorkersApp url={req.url} userAgent={req.get('User-Agent')} />;
    
    try {
      await NodeWebStreamAdapter.renderToNodeResponse(element, res, {
        onError: (error) => {
          console.error('Node.js web streams error:', error);
        }
      });
    } catch (error) {
      if (!res.headersSent) {
        res.status(500).send('渲染失败');
      }
    }
  });
  
  // 性能对比端点
  app.get('/api/stream-comparison', async (req, res) => {
    const metrics = webSSR.getMetrics();
    
    res.json({
      webStreams: metrics,
      nodeJs: {
        platform: process.platform,
        version: process.version,
        memory: process.memoryUsage()
      },
      comparison: {
        advantages: [
          'Web标准兼容',
          '边缘计算友好',
          '更好的流控制',
          '现代浏览器原生支持'
        ],
        limitations: [
          'Node.js环境需要适配',
          '生态系统相对较新',
          '某些工具库可能不兼容'
        ]
      }
    });
  });
  
  const port = process.env.PORT || 3000;
  app.listen(port, () => {
    console.log(`Node.js server with Web Streams running on port ${port}`);
  });
}

React 18 新特性深度应用

Concurrent Features 在 SSR 中的应用

React 18 引入的并发特性为 SSR 带来了革命性的性能提升,特别是在处理大型应用和复杂数据获取场景中。

import { Suspense, startTransition, useDeferredValue, useTransition } from 'react';
import { renderToPipeableStream } from 'react-dom/server';

// 并发SSR应用架构
class ConcurrentSSRApp {
  constructor() {
    this.suspenseTracker = new Map();
    this.renderPriorities = new Map();
  }
  
  // 优先级渲染控制
  renderWithPriority(element, priority = 'normal') {
    const priorityMap = {
      urgent: 1,
      normal: 2,
      background: 3
    };
    
    return new Promise((resolve, reject) => {
      const priorityValue = priorityMap[priority] || 2;
      
      // 使用优先级队列管理渲染任务
      this.scheduleRender(element, priorityValue, resolve, reject);
    });
  }
  
  scheduleRender(element, priority, resolve, reject) {
    // 模拟React的并发调度
    const renderTask = () => {
      try {
        const stream = renderToPipeableStream(element, {
          onShellReady() {
            console.log(`Priority ${priority} render shell ready`);
          },
          onAllReady() {
            console.log(`Priority ${priority} render complete`);
            resolve(stream);
          },
          onError(error) {
            console.error(`Priority ${priority} render error:`, error);
            reject(error);
          }
        });
      } catch (error) {
        reject(error);
      }
    };
    
    // 根据优先级调度任务
    if (priority === 1) {
      // 高优先级立即执行
      renderTask();
    } else if (priority === 2) {
      // 正常优先级在下一个事件循环执行
      setImmediate(renderTask);
    } else {
      // 低优先级延迟执行
      setTimeout(renderTask, 10);
    }
  }
}

// 支持并发特性的组件示例
function ConcurrentApp({ userId, filters, preferences }) {
  return (
    <html>
      <head>
        <title>并发SSR应用</title>
      </head>
      <body>
        <div id="root">
          {/* 高优先级:立即显示的内容 */}
          <Header />
          <Navigation />
          
          {/* 中优先级:主要内容区域 */}
          <main>
            <Suspense fallback={<MainContentSkeleton />}>
              <MainContent userId={userId} />
            </Suspense>
            
            {/* 低优先级:次要内容 */}
            <aside>
              <Suspense fallback={<SidebarSkeleton />}>
                <Sidebar preferences={preferences} />
              </Suspense>
            </aside>
          </main>
          
          {/* 最低优先级:底部内容 */}
          <Suspense fallback={<FooterSkeleton />}>
            <DynamicFooter />
          </Suspense>
        </div>
      </body>
    </html>
  );
}

// 智能数据预取组件
function SmartDataFetcher({ children, fallback, priority = 'normal' }) {
  const [isPending, startTransition] = useTransition();
  
  // 在服务端,直接渲染子组件
  if (typeof window === 'undefined') {
    return children;
  }
  
  // 在客户端,使用transition管理更新
  return (
    <Suspense fallback={fallback}>
      {isPending ? fallback : children}
    </Suspense>
  );
}

// 并发数据获取Hook
function useConcurrentData(fetcher, deps = []) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isPending, startTransition] = useTransition();
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchData = async () => {
      try {
        setIsLoading(true);
        setError(null);
        
        const result = await fetcher();
        
        if (!cancelled) {
          // 使用transition来更新状态,避免阻塞UI
          startTransition(() => {
            setData(result);
            setIsLoading(false);
          });
        }
      } catch (err) {
        if (!cancelled) {
          setError(err);
          setIsLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      cancelled = true;
    };
  }, deps);
  
  return { data, error, isLoading, isPending };
}

// 使用示例
const concurrentSSR = new ConcurrentSSRApp();

// Express路由with concurrent features
app.get('/concurrent/:userId', async (req, res) => {
  const { userId } = req.params;
  const { priority = 'normal' } = req.query;
  
  try {
    const appElement = (
      <ConcurrentApp 
        userId={userId}
        filters={req.query.filters}
        preferences={req.query.preferences}
      />
    );
    
    // 根据请求优先级进行渲染
    const stream = await concurrentSSR.renderWithPriority(appElement, priority);
    
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.setHeader('X-Render-Priority', priority);
    
    stream.pipe(res);
    
  } catch (error) {
    console.error('Concurrent SSR error:', error);
    res.status(500).send('并发渲染失败');
  }
});

总结

React 服务端渲染的 Server API 为构建高性能 Web 应用提供了强大的工具集。通过本文的深入解析,我们全面覆盖了所有 renderToXX 方法:

核心 API 总结

  1. renderToString - 传统同步渲染,适用于简单应用
  2. renderToStaticMarkup - 纯静态内容生成,体积更小
  3. renderToNodeStream - Node.js流式渲染,支持背压处理
  4. renderToStaticNodeStream - 静态内容流,无React属性
  5. renderToPipeableStream - React 18现代流式API,完整并发支持
  6. renderToReadableStream - Web标准流,边缘计算友好

技术选择指南

  • 传统应用: 使用 renderToString 进行快速迁移
  • 静态站点: 使用 renderToStaticMarkup 获得最小输出
  • 大型应用: 使用 renderToPipeableStream 获得最佳性能
  • 边缘计算: 使用 renderToReadableStream 实现全球分发

最佳实践

  1. 渐进式升级: 从传统API逐步迁移到React 18新特性
  2. 合理选择: 根据应用场景选择最适合的渲染方法
  3. 性能监控: 建立完整的渲染性能监控体系
  4. 错误处理: 实现完善的错误处理和降级机制

通过掌握这些 Server API,开发者能够构建出高性能、可扩展的服务端渲染应用,为用户提供优秀的访问体验。