什么是静态生成(SSG)及其在游戏社区中的重要性

静态生成(Static Generation)是Next.js中一种在构建时生成HTML页面的技术,与传统的服务器端渲染(SSR)不同,SSG在构建阶段就预先生成所有页面的静态HTML文件,这些文件可以直接被CDN缓存,从而提供极快的加载速度。对于游戏攻略社区这类内容密集型网站,SSG具有显著优势。

静态生成的核心优势

  1. 性能提升:静态HTML文件无需服务器实时渲染,加载速度比SSR快3-5倍
  2. SEO优化:搜索引擎爬虫可以直接抓取完整的HTML内容,无需执行JavaScript
  3. 成本降低:可以部署到静态托管服务(如Vercel、Netlify、GitHub Pages),无需昂贵的服务器资源
  4. 安全性:没有服务器端代码执行,攻击面大幅减少

游戏社区的特殊需求

游戏攻略社区通常包含:

  • 大量游戏攻略文章(如《原神》角色培养指南、《艾尔登法环》BOSS攻略)
  • 游戏数据库(武器、道具、角色属性)
  • 用户评论和评分系统
  • 实时数据(如游戏版本更新、活动信息)

通过SSG,我们可以将大部分内容静态化,只保留用户交互部分动态处理,实现性能与功能的平衡。

Next.js静态生成的核心API

getStaticPaths:预生成动态路由

对于游戏攻略社区,每个游戏、每个攻略文章都有独立的URL,如:

  • /games/genshin-impact
  • /games/genshin-impact/characters/zhongli

使用getStaticPaths可以预先生成所有可能的页面路径:

// pages/games/[gameId]/[攻略Id].js
export async function getStaticPaths() {
  // 从数据库或API获取所有游戏和攻略的ID
  const games = await fetch('https://api.your-community.com/games').then(res => res.json());
  
  const paths = [];
  
  for (const game of games) {
    // 获取该游戏的所有攻略
    const guides = await fetch(`https://api.your-community.com/games/${game.id}/guides`).then(res => res.json());
    
    for (const guide of guides) {
      paths.push({
        params: { 
          gameId: game.id,
          guideId: guide.id 
        }
      });
    }
  }
  
  return {
    paths,
    fallback: 'blocking' // 或者 true/false,根据需求选择
  };
}

getStaticProps:获取静态数据

getStaticProps用于在构建时获取页面所需的数据:

// pages/games/[gameId]/[guideId].js
export async function getStaticProps(context) {
  const { gameId, guideId } = context.params;
  
  // 获取攻略详情
  const guide = await fetch(`https://api.your-community.com/games/${gameId}/guides/${guideId}`).then(res => res.json());
  
  // 获取游戏基本信息
  const game = await fetch(`https://api.your-community.com/games/${gameId}`).then(res => res.json());
  
  // 获取相关攻略推荐(可选)
  const relatedGuides = await fetch(`https://api.your-community.com/games/${gameId}/guides?relatedTo=${guideId}`).then(res => res.json());
  
  return {
    props: {
      guide,
      game,
      relatedGuides
    },
    // 重新生成时间:每小时重新构建一次,适合攻略内容更新
    revalidate: 3600
  };
}

fallback:处理未预生成的页面

fallback选项有三个值:

  • false:只生成getStaticPaths返回的路径,其他返回404
  • true:首次访问未预生成的页面时,显示fallback UI,后台生成页面并缓存
  • blocking:首次访问时后台生成页面,用户等待生成完成

对于游戏社区,推荐使用blocking

export async function getStaticPaths() {
  // 只预生成热门游戏的热门攻略,减少构建时间
  const popularGames = await fetch('https://api.your-community.com/games?popular=true&limit=50').then(res => res.json());
  
  const paths = [];
  
  for (const game of popularGames) {
    const popularGuides = await fetch(`https://api.your-community.com/games/${game.id}/guides?popular=true&limit=20`).then(res => res.json());
    
    for (const guide of popularGuides) {
      paths.push({
        params: { 
          gameId: game.id,
          guideId: guide.id 
        }
      });
    }
  }
  
  return {
    paths,
    fallback: 'blocking'
  };
}

实战:构建游戏攻略社区的SSG架构

1. 项目结构设计

/my-game-community
├── /pages
│   ├── /games
│   │   ├── [gameId]
│   │   │   ├── index.js          # 游戏主页
│   │   │   ├── [guideId].js     # 攻略详情页
│   │   │   └── characters
│   │   │       └── [characterId].js # 角色详情页
│   │   └── index.js             # 游戏列表页
│   ├── /api
│   │   ├── comments.js          # 评论API(动态)
│   │   └── likes.js             # 点赞API(动态)
│   └── index.js                 # 首页
├── /components
│   ├── /GuideCard.js            # 攻略卡片组件
│   ├── /GameList.js             # 游戏列表组件
│   └── /CommentSection.js       # 评论区组件(客户端动态)
├── /lib
│   ├── /api.js                  # API调用封装
│   └── /seo.js                  # SEO工具函数
└── /public
    └── /images                  # 静态图片资源

2. 首页静态生成实现

// pages/index.js
import Link from 'next/link';
import GameList from '../components/GameList';

export async function getStaticProps() {
  // 获取热门游戏列表
  const popularGames = await fetch('https://api.your-community.com/games?popular=true&limit=20').then(res => res.json());
  
  // 获取最新发布的攻略
  const latestGuides = await fetch('https://api.your-community.com/guides?sort=newest&limit=10').then(res => res.json());
  
  // 获取热门攻略(按浏览量)
  const trendingGuides = await fetch('https://api.your-community.com/guides?sort=views&limit=10').then(res => res.json());
  
  return {
    props: {
      popularGames,
      latestGuides,
      trendingGuides
    },
    revalidate: 3600 // 每小时重新生成
  };
}

export default function HomePage({ popularGames, latestGuides, trendingGuides }) {
  return (
    <div className="container">
      <h1>游戏攻略社区</h1>
      
      <section>
        <h2>热门游戏</h2>
        <GameList games={popularGames} />
      </section>
      
      <section>
        <h2>最新攻略</h2>
        <ul>
          {latestGuides.map(guide => (
            <li key={guide.id}>
              <Link href={`/games/${guide.gameId}/${guide.id}`}>
                <a>{guide.title}</a>
              </Link>
            </li>
          ))}
        </ul>
      </section>
      
      <section>
        <h2>热门攻略</h2>
        <ul>
          {trendingGuides.map(guide => (
            <li key={guide.id}>
              <Link href={`/games/${guide.gameId}/${guide.id}`}>
                <a>{guide.title}</a>
              </Link>
            </li>
          ))}
        </ul>
      </section>
    </div>
  );
}

3. 游戏主页静态生成

// pages/games/[gameId]/index.js
import Link from 'next/link';
import Head from 'next/head';

export async function getStaticPaths() {
  // 只预生成热门游戏,其他游戏使用fallback
  const games = await fetch('https://api.your-community.com/games?popular=true').then(res => res.json());
  
  return {
    paths: games.map(game => ({
      params: { gameId: game.id }
    })),
    fallback: 'blocking'
  };
}

export async function getStaticProps(context) {
  const { gameId } = context.params;
  
  // 获取游戏详情
  const game = await fetch(`https://api.your-community.com/games/${gameId}`).then(res => res.json());
  
  // 获取该游戏的所有攻略分类
  const guideCategories = await fetch(`https://api.your-community.com/games/${gameId}/guides/categories`).then(res => res.json());
  
  // 获取热门攻略
  const popularGuides = await fetch(`https://api.your-community.com/games/${gameId}/guides?popular=true&limit=10`).then(res => res.json());
  
  // 获取游戏角色列表
  const characters = await fetch(`https://api.your-community.com/games/${gameId}/characters`).then(res => res.json());
  
  return {
    props: {
      game,
      guideCategories,
      popularGuides,
      characters
    },
    revalidate: 3600
  };
}

export default function GamePage({ game, guideCategories, popularGuides, characters }) {
  return (
    <div className="container">
      <Head>
        <title>{game.name} 攻略 - 游戏攻略社区</title>
        <meta name="description" content={`${game.name} 游戏攻略、角色指南、通关技巧,尽在游戏攻略社区`} />
      </Head>
      
      <h1>{game.name}</h1>
      <p>{game.description}</p>
      
      <section>
        <h2>攻略分类</h2>
        <ul>
          {guideCategories.map(category => (
            <li key={category.id}>
              <Link href={`/games/${game.id}/guides/${category.id}`}>
                <a>{category.name}</a>
              </Link>
            </li>
          ))}
        </ul>
      </section>
      
      <section>
        <h2>热门攻略</h2>
        <ul>
          {popularGuides.map(guide => (
            <li key={guide.id}>
              <Link href={`/games/${game.id}/${guide.id}`}>
                <a>{guide.title}</a>
              </Link>
              <span>浏览量: {guide.views}</span>
            </li>
          ))}
        </ul>
     .js
      </section>
      
      <section>
        <h2>游戏角色</h2>
        <ul>
          {characters.map(character => (
            <li key={character.id}>
              <Link href={`/games/${game.id}/characters/${character.id}`}>
                <a>{character.name}</a>
              </Link>
            </li>
          ))}
       详情
        </ul>
      </section>
    </div>
  );
}

4. 攻略详情页静态生成

// pages/games/[gameId]/[guideId].js
import Head from 'next/head';
import { useEffect, useState } from 'react';
import CommentSection from '../../components/CommentSection';

export async function getStaticPaths() {
  // 预生成热门游戏的热门攻略
  const popularGames = await fetch('https://api.your-community.com/games?popular=true&limit=20').then(res => res.json());
  
  const paths = [];
  
  for (const game of popularGames) {
    const popularGuides = await fetch(`https://api.your-community.com/games/${game.id}/guides?popular=true&limit=30`).then(res => res.json());
    
    for (const guide of popularGuides) {
      paths.push({
        params: { 
          gameId: game.id,
          guideId: guide.id 
        }
      });
    }
  }
  
  return {
    paths,
    fallback: 'blocking'
  };
}

export async function getStaticProps(context) {
  const { gameId, guideId } = context.params;
  
  // 获取攻略详情
  const guide = await fetch(`https://api.your-community.com/games/${gameId}/guides/${guideId}`).then(res => res.json());
  
  // 获取游戏信息
  const game = await fetch(`https://api.your-community.com/games/${gameId}`).then(res => res.json());
  
  // 获取相关攻略推荐
  const relatedGuides = await fetch(`https://api.your-community.com/games/${gameId}/guides?relatedTo=${guideId}&limit=5`).then(res => res.json());
  
  // 获取作者信息
  const author = await fetch(`https://api.your-community.com/users/${guide.authorId}`).then(res => res.json());
  
  return {
    props: {
      guide,
      game,
      relatedGuides,
      author
    },
    revalidate: 3600 // 每小时重新生成
  };
}

export default function GuidePage({ guide, game, relatedGuides, author }) {
  // 使用客户端状态管理评论和点赞(动态部分)
  const [comments, setComments] = useState([]);
  const [likes, setLikes] = useState(guide.likes || 0);
  
  // 这里可以调用API获取实时评论数据
  useEffect(() => {
    // 评论和点赞数据在客户端获取,避免影响SSG
    fetch(`/api/comments?guideId=${guide.id}`)
      .then(res => res.json())
      .then(data => setComments(data));
  }, [guide.id]);
  
  return (
    <div className="container">
      <Head>
        <title>{guide.title} - {game.name} 攻略</title>
        <meta name="description" content={guide.excerpt || guide.content.substring(0, 150)} />
        <meta property="og:title" content={guide.title} />
        <meta property="og:description" content={guide.excerpt || guide.content.substring(0, 150)} />
        <meta property="og:type" content="article" />
        <meta property="og:url" content={`https://your-community.com/games/${game.id}/${guide.id}`} />
      </Head>
      
      <article>
        <header>
          <h1>{guide.title}</h1>
          <div>
            <span>作者: {author.name}</span>
            <span>发布日期: {new Date(guide.publishedAt).toLocaleDateString()}</span>
            <span>浏览量: {guide.views}</span>
            <button onClick={() => setLikes(likes + 1)}>点赞 {likes}</button>
          </div>
        </header>
        
        <div className="content" dangerouslySetInnerHTML={{ __html: guide.content }} />
        
        <footer>
          <h3>相关攻略</h3>
          <ul>
            {relatedGuides.map(related => (
              <li key={related.id}>
                <Link href={`/games/${game.id}/${related.id}`}>
                  <a>{related.title}</a>
                </Link>
              </li>
            ))}
          </ul>
        </footer>
      </article>
      
      {/* 评论区作为动态组件 */}
      <CommentSection guideId={guide.id} initialComments={comments} />
    </div>
  );
}

高级SSG优化策略

1. 增量静态再生(ISR)

ISR允许你在页面生成后定期更新内容,而无需重新构建整个应用:

export async function getStaticProps() {
  const data = await fetch('https://api.your-community.com/guides').then(res => res.json());
  
  return {
    props: {
      guides: data
    },
    revalidate: 3600, // 每小时重新生成一次
    // 或者使用更精细的控制:
    // revalidate: {
    //   'games/[gameId]': 3600,
    //   'games/[gameId]/[guideId]': 7200
    // }
  };
}

2. 部分预渲染(Partial Prerendering)- Next.js 14+

Next.js 14+ 引入了部分预渲染,允许在静态页面中嵌入动态部分:

// pages/guides/[guideId].js
import { Suspense } from 'react';
import GuideContent from '../../components/GuideContent';
import CommentSection from '../../components/CommentSection';

export async function getStaticPaths() {
  // ... 路径生成逻辑
  return { paths, fallback: 'blocking' };
}

export async function getStaticProps(context) {
  // ... 获取静态数据
  return { props: { guide }, revalidate: 3600 };
}

export default function GuidePage({ guide }) {
  return (
    <div>
      {/* 静态内容 */}
      <GuideContent guide={guide} />
      
      {/* 动态评论部分 - 使用Suspense包裹 */}
      <Suspense fallback={<div>加载评论中...</div>}>
        <CommentSection guideId={guide.id} />
      </Suspense>
    </div>
  );
}

3. 动态生成参数处理

对于游戏社区,有些页面可能需要根据用户输入动态生成,可以使用getStaticPathsfallback机制:

export async function getStaticPaths() {
  // 只预生成已知的游戏和攻略
  const knownGames = ['genshin-impact', 'elden-ring', 'star-rail'];
  const paths = [];
  
  for (const game of knownGames) {
    // 获取该游戏的热门攻略
    const guides = await fetch(`https://api.your-community.com/games/${game}/guides?popular=true&limit=10`).then(res => res.json());
    
    for (const guide of guides) {
      paths.push({
        params: { gameId: game, guideId: guide.id }
      });
    }
  }
  
  return {
    paths,
    fallback: 'blocking' // 其他页面首次访问时生成
  };
}

4. 使用getStaticPropscontext参数

export async function getStaticProps(context) {
  const { params, preview, previewData } = context;
  
  // preview和previewData用于预览模式
  if (preview) {
    // 获取草稿内容
    const draftGuide = await fetch(`https://api.your-community.com/guides/${params.guideId}?draft=true`, {
      headers: { Authorization: `Bearer ${previewData.token}` }
    }).then(res => res.json());
    
    return {
      props: {
        guide: draftGuide,
        isPreview: true
      },
      revalidate: 1 // 预览模式下缩短重新生成时间
    };
  }
  
  // 正常模式
  const guide = await fetch(`https://api.your-community.com/guides/${params.guideId}`).then(res => res.json());
  
  return {
    props: { guide },
    revalidate: 3600
  };
}

SEO优化最佳实践

1. 结构化数据(Schema.org)

// lib/seo.js
export function generateGameSchema(game) {
  return {
    "@context": "https://schema.org",
    "@type": "VideoGame",
    "name": game.name,
    "description": game.description,
    "genre": game.genre,
    "operatingSystem": game.platforms,
    "datePublished": game.releaseDate,
    "aggregateRating": {
      "@type": "AggregateRating",
      "ratingValue": game.rating,
      "reviewCount": game.reviewCount
    }
  };
}

export function generateArticleSchema(guide, game, author) {
  return {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": guide.title,
    "description": guide.excerpt,
    "image": guide.thumbnail,
    "datePublished": guide.publishedAt,
    "dateModified": guide.updatedAt,
    "author": {
      "@type": "Person",
      "name": author.name
    },
    "publisher": {
      "@type": "Organization",
      "name": "游戏攻略社区",
      "logo": {
        "@type": "ImageObject",
        "url": "https://your-community.com/logo.png"
      }
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": `https://your-community.com/games/${game.id}/${guide.id}`
    }
  };
}

在页面中使用:

// pages/games/[gameId]/[guideId].js
import Head from 'next/head';
import { generateArticleSchema } from '../../lib/seo';

export default function GuidePage({ guide, game, author }) {
  const schema = generateArticleSchema(guide, game, author);
  
  return (
    <>
      <Head>
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
        />
      </Head>
      {/* 页面内容 */}
    </>
  );
}

2. 优化元标签和Open Graph

// lib/seo.js
export function generateMetaTags({ title, description, image, url, type = 'website' }) {
  return {
    title: `${title} - 游戏攻略社区`,
    meta: [
      { name: 'description', content: description },
      { property: 'og:title', content: title },
      { property: 'og:description', content: description },
      { property: 'og:image', content: image || '/default-og-image.jpg' },
      { property: 'og:url', content: url },
      { property: 'og:type', content: type },
      { name: 'twitter:card', content: 'summary_large_image' },
      { name: 'twitter:title', content: title },
      { name: 'twitter:description', content: description },
      { name: 'twitter:image', content: image || '/default-og-image.jpg' }
    ]
  };
}

3. 生成站点地图(Sitemap)

// pages/sitemap.xml.js
export async function getServerSideProps({ res }) {
  // 获取所有游戏和攻略的URL
  const games = await fetch('https://api.your-community.com/games').then(res => res.json());
  
  const urls = [
    { loc: 'https://your-community.com/', priority: 1.0 },
    { loc: 'https://your-community.com/games', priority: 0.9 }
  ];
  
  for (const game of games) {
    urls.push({
      loc: `https://your-community.com/games/${game.id}`,
      priority: 0.8
    });
    
    const guides = await fetch(`https://api.your-community.com/games/${game.id}/guides`).then(res => res.json());
    
    for (const guide of guides) {
      urls.push({
        loc: `https://your-community.com/games/${game.id}/${guide.id}`,
        priority: 0.7,
        lastmod: guide.updatedAt
      });
    }
  }
  
  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  ${urls.map(url => `
    <url>
      <loc>${url.loc}</loc>
      ${url.lastmod ? `<lastmod>${url.lastmod}</lastmod>` : ''}
      <priority>${url.priority}</priority>
    </url>
  `).join('')}
</urlset>`;
  
  res.setHeader('Content-Type', 'text/xml');
  res.write(sitemap);
  res.end();
  
  return { props: {} };
}

export default function Sitemap() {
  return null;
}

4. 机器人元标签和规范URL

// lib/seo.js
export function generateRobotsMeta() {
  return {
    robots: 'index, follow',
    googlebot: 'index, follow'
  };
}

export function generateCanonicalUrl(path) {
  return `https://your-community.com${path}`;
}

在页面中使用:

import Head from 'next/head';
import { generateCanonicalUrl, generateRobotsMeta } from '../lib/seo';

export default function Page({ canonicalPath }) {
  return (
    <Head>
      <link rel="canonical" href={generateCanonicalUrl(canonicalPath)} />
      <meta name="robots" content={generateRobotsMeta().robots} />
    </Head>
  );
}

性能优化与CDN部署

1. 图片优化

// next.config.js
module.exports = {
  images: {
    domains: ['your-cdn.com', 'api.your-community.com'],
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 86400 // 24小时缓存
  }
};

2. 缓存策略

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/_next/static/(.*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable'
          }
        ]
      },
      {
        source: '/static/(.*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable'
          }
        ]
      },
      {
        source: '/api/(.*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'no-store, max-age=0'
          }
        ]
      }
    ];
  }
};

3. 部署到Vercel(推荐)

# 安装Vercel CLI
npm i -g vercel

# 部署
vercel --prod

Vercel会自动:

  • 配置全球CDN
  • 启用HTTP/2和HTTP/3
  • 自动优化静态资源
  • 提供分析仪表板

4. 自定义CDN配置

如果使用其他CDN(如Cloudflare、AWS CloudFront),需要配置:

// next.config.js
module.exports = {
  // 如果使用子路径
  basePath: '',
  
  // 如果使用自定义域名
  assetPrefix: 'https://cdn.your-community.com',
  
  // 生成静态导出(如果需要)
  // output: 'export', // Next.js 13.4+
};

动态内容处理策略

1. 用户评论和点赞(客户端动态)

// components/CommentSection.js
'use client';

import { useState, useEffect } from 'react';

export default function CommentSection({ guideId, initialComments }) {
  const [comments, setComments] = useState(initialComments || []);
  const [newComment, setNewComment] = useState('');
  
  useEffect(() => {
    // 客户端获取最新评论
    fetch(`/api/comments?guideId=${guideId}`)
      .then(res => res.json())
      .then(data => setComments(data));
  }, [guideId]);
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const response = await fetch('/api/comments', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ guideId, content: newComment })
    });
    
    const savedComment = await response.json();
    setComments(prev => [savedComment, ...prev]);
    setNewComment('');
  };
  
  return (
    <div>
      <h3>评论 ({comments.length})</h3>
      <form onSubmit={handleSubmit}>
        <textarea 
          value={newComment} 
          onChange={e => setNewComment(e.target.value)}
          placeholder="分享你的攻略心得..."
          required
        />
        <button type="submit">发表评论</button>
      </form>
      
      <ul>
        {comments.map(comment => (
          <li key={comment.id}>
            <strong>{comment.author}:</strong> {comment.content}
          </li>
        ))}
      </ul>
    </div>
  );
}

2. 实时数据API路由

// pages/api/comments.js
export default async function handler(req, res) {
  if (req.method === 'GET') {
    const { guideId } = req.query;
    
    // 从数据库获取评论
    const comments = await fetch(`https://api.your-community.com/comments?guideId=${guideId}`)
      .then(res => res.json());
    
    res.status(200).json(comments);
  } else if (req.method === 'POST') {
    const { guideId, content } = req.body;
    
    // 保存评论到数据库
    const newComment = await fetch('https://api.your-community.com/comments', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ guideId, content, author: '匿名用户' })
    }).then(res => res.json());
    
    res.status(201).json(newComment);
  }
}

3. 用户认证与个性化内容

// middleware.js
import { NextResponse } from 'next/server';

export function middleware(req) {
  const token = req.cookies.get('auth-token');
  
  // 如果用户访问需要登录的页面但未登录,重定向到登录页
  if (req.nextUrl.pathname.startsWith('/user') && !token) {
    return NextResponse.redirect(new URL('/login', req.url));
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ['/user/:path*', '/api/user/:path*']
};

构建优化与监控

1. 构建时间优化

// next.config.js
module.exports = {
  // 减少构建时间
  experimental: {
    optimizePackageImports: ['lucide-react', 'date-fns'],
  },
  
  // 编译缓存
  swcMinify: true,
  
  // 排除不必要的包
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        fs: false,
        net: false,
        tls: false,
      };
    }
    return config;
  }
};

2. 构建分析

# 安装分析工具
npm install --save-dev @next/bundle-analyzer

# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true'
});

module.exports = withBundleAnalyzer({});

运行分析:

ANALYZE=true npm run build

3. 监控与告警

// pages/_app.js
import { useEffect } from 'react';

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    // 监控页面性能
    if ('performance' in window) {
      window.addEventListener('load', () => {
        const perfData = performance.getEntriesByType('navigation')[0];
        console.log('页面加载时间:', perfData.loadEventEnd - perfData.fetchStart);
        
        // 发送到监控服务
        if (perfData.loadEventEnd - perfData.fetchStart > 3000) {
          // 发送告警
          fetch('/api/alerts', {
            method: 'POST',
            body: JSON.stringify({
              type: 'slow_page',
              url: window.location.href,
              loadTime: perfData.loadEventEnd - perfData.fetchStart
            })
          });
        }
      });
    }
  }, []);
  
  return <Component {...pageProps} />;
}

export default MyApp;

总结

通过Next.js的静态生成功能,游戏攻略社区可以实现:

  1. 极致性能:静态HTML文件配合CDN,首屏加载时间秒
  2. 完美SEO:完整的HTML内容,结构化数据,站点地图
  3. 成本效益:无需昂贵服务器,可部署到免费/低成本静态托管
  4. 灵活扩展:通过ISR和fallback机制处理内容更新和新增页面
  5. 动态交互:保留用户评论、点赞等实时功能

关键实施步骤:

  1. 使用getStaticPaths预生成热门页面
  2. 使用getStaticProps获取静态数据并设置合理的revalidate时间
  3. 使用fallback: 'blocking'处理长尾页面
  4. 将动态功能(评论、认证)分离到客户端或API路由
  5. 优化SEO元标签、结构化数据和站点地图
  6. 配置合适的缓存策略和CDN

这种方法特别适合游戏攻略社区这类内容密集、读多写少的场景,能够在保证性能的同时提供良好的用户体验。