Eleventy和Next.js都是目前前端社区里热门的静态站点生成方案,但两者的设计哲学完全不同。Eleventy是一个与框架无关的静态站点生成器,不依赖任何前端运行时框架,可以用Liquid、Nunjucks、Handlebars甚至Markdown来组织页面。Next.js则建立在React之上,提供了SSG、SSR、ISR等多种渲染模式的混合能力。本文从架构、数据获取、渲染策略和迁移实操几个角度做一次完整的对比,帮助已经使用Eleventy的团队评估是否有必要迁移到Next.js。

架构设计理念的根本差异
Eleventy的核心卖点是零客户端JavaScript。它在构建时把模板和数据处理成纯HTML文件,页面上没有任何框架运行时,加载速度天然很快。开发者可以根据内容类型自由选择模板语言,比如博客文章用Markdown,列表页用Nunjucks,这种灵活性是Eleventy深受内容型站点欢迎的原因。它的构建产物就是一个静态文件目录,可以直接托管在任何CDN上,运维成本极低。
Next.js则完全围绕React展开。页面即组件,所有内容最终都会挂载到React的组件树上。这意味着项目天然具备向SSR和ISR演进的能力,不需要在构建时一次性生成全部页面。Next.js通过路由约定来组织文件,页面文件导出一个React组件,配合getStaticProps或服务端组件的数据获取方式,可以在构建期生成静态HTML,也可以在请求期动态渲染。
从架构角度看,两者并不是简单的竞争关系。Eleventy适合以内容展示为主、交互较少的网站,比如博客、文档站、企业官网。Next.js适合已经使用React技术栈、后续可能引入更多交互功能的应用,比如需要登录态的页面、搜索、评论等。如果团队完全不需要客户端框架能力,Eleventy仍然是更轻的选择。
数据获取与页面渲染策略对比
Eleventy的数据层比较朴素,它会把目录中的数据文件、JSON文件以及集合机制汇总成数据级联,模板中直接读取。它还有强大但学习曲线偏陡的集合系统,可以用标签对内容做筛选和排序。整条数据链路是同步的、构建期确定的,一旦内容有更新就需要重新构建整个站点。
Next.js的数据获取方式要丰富得多。下面是一个典型的静态生成页面示例,展示如何在构建期从远程接口拉取文章列表并预渲染:
// app/blog/page.js (Next.js App Router示例)
async function getPosts() {
const res = await fetch('https://api.ipipp.com/posts', {
next: { revalidate: 3600 } // 每小时重新验证,启用ISR能力
});
if (!res.ok) throw new Error('获取文章失败');
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<main>
<h1>文章列表</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<a href={`/blog/${post.slug}`}>{post.title}</li>
</a>
))}
</ul>
</main>
);
}
这段代码里的revalidate参数是Eleventy不具备的能力:增量静态再生。它允许单个页面在指定时间间隔后自动重新生成,而不需要整站重建。对于文章数量很大的站点,这个特性可以显著缩短发布周期。Eleventy要做到类似效果,往往需要配合增构建脚本或第三方服务做增量构建,工程复杂度更高。
在渲染策略上,Next.js还支持在同一个项目中混用静态和动态路由。比如详情页是静态生成的,而用户中心页面用动态渲染,这种混合模式在Eleventy中是不可能实现的,因为Eleventy的产物永远是纯静态文件。
从Eleventy迁移到Next.js的实操步骤
迁移的第一步是梳理现有内容结构。Eleventy项目通常有collections、templates和data目录,对应到Next.js中,需要把Markdown内容保留下来,用MDX或front-matter解析库读取。推荐使用gray-matter解析文章的元数据,配合remark或rehype把Markdown转换成React可渲染的HTML。
第二步是转换布局系统。Eleventy的Nunjucks布局模板一般长这样:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>{{ title }}</title>
</head>
<body>
{% include "header.html" %}
<main>{{ content | safe }}</main>
{% include "footer.html" %}
</body>
</html>
迁移到Next.js后,对应的布局会变成React组件的形式。在App Router中,直接在app目录下放置layout.js文件,框架会自动为所有子路由应用这个布局:
// app/layout.js
import Header from '../components/Header';
import Footer from '../components/Footer';
import './globals.css';
export const metadata = {
title: '我的博客',
description: '基于Next.js重建的静态站点'
};
export default function RootLayout({ children }) {
return (
<html lang="zh-CN">
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);
}
第三步是重建动态路由。Eleventy中的分页配置对应Next.js的generateStaticParams函数。Eleventy通过pagination在模板里声明分页规则,Next.js则在路由文件中导出一个异步函数,返回所有可能的路径参数,构建时框架据此逐一生成静态页面。
// app/blog/[slug]/page.js
import { getPostSlugs, getPostBySlug } from '../../../lib/posts';
export async function generateStaticParams() {
const slugs = getPostSlugs(); // 返回所有文章的slug数组
return slugs.map(slug => ({ slug }));
}
export default async function PostPage({ params }) {
const post = await getPostBySlug(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
);
}
迁移过程中还有几个细节需要注意。其一是URL结构要完全保持一致,否则已有站点的搜索引擎排名会受影响,必要时配置redirects规则做重定向。其二是Eleventy中的shortcodes和filters需要改写成React组件或工具函数,这部分工作量取决于项目的复杂程度。其三是性能核对,迁移完成后用Lighthouse对比迁移前后的指标,确认没有引入不必要的客户端JavaScript,如果只是纯静态页面,尽量保持组件在服务端渲染,避免随意添加use client标记。
如何判断是否真的需要迁移
并不是所有Eleventy项目都应该迁移到Next.js。如果站点纯粹是内容展示,没有登录、搜索、个性化推荐这类需要服务端或客户端逻辑的功能,Eleventy的构建产物更小、部署更简单,继续使用反而更合理。Eleventy本身也在持续演进,3.x版本切换到底层构建工具后性能有明显提升。
真正值得迁移的信号通常有三个。第一,团队已经深度使用React,希望统一技术栈,减少维护两套模板体系的成本。第二,业务对渲染模式有混合需求,部分页面需要请求期动态渲染或增量再生。第三,希望利用Next.js丰富的生态,比如图像优化组件、中间件、边缘函数等能力,用官方方案替代自己拼装的第三方插件。
总体来看,Eleventy和Next.js代表了静态站点生成的两种思路:前者追求极简和零运行时开销,后者追求框架生态的完整性和渲染策略的灵活性。选型的关键不是哪个更流行,而是项目的实际需求和团队的技术背景。对于决定迁移的项目,按照内容解析、布局转换、路由重建的顺序分阶段推进,同时严守URL一致性,可以大大降低迁移风险,让切换过程平稳可控。