大家好,很高兴又见面了,我是" 高级前端进阶 ",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

高级前端进阶
今天给大家带来的主题是Nextjs、Remix,各自特点、全方位差异比较。话不多说,直接开始!
前言
当想要基于 React 创建一个新的 Web 项目时,有许多不同的框架可以选择。 作为一名前端开发人员,您会发现自己很难知道应该选择哪一个框架,或者哪一个框架最适合开发需求。

图片来自:https://dormoshe.io/daily-news/remix-vs-next-js-a-detailed-comparison-2231.2
最常用的框架之一是 Next.js,Netflix、Twitch 或 Uber 等公司都已经在生产项目中使用它, Next.js 被认为是增长最快的 React 框架之一。其他框架很难与 Next.js 媲美,因为它涵盖了三种不同的页面渲染策略。但自 2021 年 11 月以来,前端又多了一个全新的、强大的备选框架,称为 Remix。本篇文章将重点聚焦在 Remix、Next.js 框架的比较上。
1.为何需要 Next.js 或 Remix 而不是纯 React
React 可以制作单页应用程序 (SPA),其仅使用一个 HTML 文件来呈现所有网页,路由由客户端控制。
- 当客户端发出初始请求时,浏览器会立即收到一个包含所有应用程序的 HTML 页面,没有预渲染内容
- 当用户在应用导航时,JavaScript 仅替换那些与请求路由相关的组件和内容,但 HTML 文件保持保持不变
简而言之,浏览器负责管理应加载哪个 JavaScript 文件以渲染当前页面,这就是客户端渲染 ,即 CSR。但是,CSR 也存在诸多限制:
- Slow Initial Render:当用户第一次访问页面时,需要很长时间才能加载完成。 这意味着较长的空白页面,直到 JavaScript 加载并渲染所有内容。
- SEO:因为初始时只有一个 HTML 页面,所以很难让搜索引擎对内容进行索引。
- 缓存问题:HTML 结构无法缓存,因为它在第一次渲染时不可用。
由于 CSR 的三个核心问题,Next.js 和 Remix 出现了, 它们可以在发送客户端请求之前在服务器端渲染内容,从而最大限度的缩短页面白屏时间,提升用户体验。
2.SSR vs SSG vs ISR
除了 CSR ,还有诸如 SSR 、 SSG 、 ISR 等其他页面渲染策略。Next.js 提供了一个强大的配置选项,支持 3 个不同的开箱即用的渲染方式,而 Remix 完全依赖于 SSR(至少目前)。

图片来自:https://tapajyoti-bose.medium.com/
2.1 服务器端渲染 (SSR)
SSR,即 Server-Side Rendering。当一个页面被请求时,它在发送到客户端之前在服务器上完成渲染。 对于那些拥有大量动态数据和内容的网站来说,这是一个很好的选择。
2.2 静态站点生成 (SSG)
SSG,即 Static Site Generation ,该策略在构建期间按路由生成所有页面内容。 SSG 只会将当前被请求的页面发送给客户端,这构建了一个典型的静态网站。
对于那些几乎没有动态数据且页面不多的静态网站来说,SSG是一个合适的选择。 但是,需要注意的是,数据的更改都会触发所有页面的重新生成。
2.3 增量静态再生 (ISR)
ISR,即 Incremental Static Regeneration。Next.js 引入了这个概念,其是 SSR 和 SSG 的混合体。它类似于 SSG,但开发者可以设置一个间隔时间,用于控制何时应该重新生成页面。 适用于那些具有动态内容的静态站点。
Next.js 的优势在于允许开发者在每个页面上的 3 个选项之间灵活切换、选择。所以也许你会问:“如果 Remix 只适用于 SSR,为何要用它替换 Next.js”。 答案很简单,如今的应用程序和网站往往具有动态数据,并且很少有适合 SSG 或 ISR 场景的示例。
// 1.开放底层Request对象实例
export async function loader({ request }: LoaderArgs) {
// 读取 cookie
const cookie = request.headers.get("Cookie");
// 解析 `?q=` 的搜索参数
const url = new URL(request.url);
const query = url.searchParams.get("q");
}
// 2.开放底层Response对象实例
export async function loader() {
const users = await db.users.findMany();
const body = JSON.stringify(users);
return new Response(body, {
headers: {
"Content-Type": "application/json",
},
});
}
Remix 提供了一个较底层的 API。 例如:公开了 Request /Reponse对象,因此开发者可以在渲染页面之前轻松修改 Headers,或者在返回客户端请求之前轻松修改Response,而在 Next.js 中,一切都需要开发者借助中间件来实现它。
2.4 Routing 路由
Next.js 和 Remix 框架都使用基于文件的路由系统来渲染页面:
- 每个页面都保留在不同的文件中
- 每个页面都与基于其文件名的路由相关联
- 每个页面都会渲染一个 React 组件。
Remix 中路由
在 /app/routes 中添加每个路由,在其中创建的每个文件或文件夹都将被视为路由。可以在 /app/root.jsx 中可以找到一个文件,它是“根路由”,是整个应用程序的布局。
Next.js 中路由
可以在 /pages 中添加路由,它的工作方式与 Remix 完全相同。 pages/_app.js 和 pages/_document.js 可有可无。 其中 App 用于初始化页面,Document 包含一系列标签。
Next.js 默认使用它们,无需额外配置。 但是,如果开发者需要自定义,可以创建这两个文件,以这种方式覆盖默认文件。
Index 路由
Remix 和 Next.js 都使用相同的系统来呈现容器路由(container route)。
Next.js 示例:
- pages/index.js => /(根页面)
- pages/posts/index.js 或 pages/posts.js => /posts
Remix 示例:
- routes/index.js => /(根页面)
- routes/posts/index.js 或 routes/posts.js => /posts
嵌套路由
Remix 建立在 React Router 之上,由同一个团队开发维护,所以在这一点上 Remix 貌似更胜一筹。Remix 背后的想法是创建一个活动路由来挂载包含多个嵌套路由的布局,因此每个路由负责管理自己的内容。
如果必须用 React 思路来解释,每个嵌套路由其实都相当于组件,并且根据 URL 路径可以完成组件动态挂载和卸载。
为实现此目的,Remix 使用了 <Outlet /> React-router 组件。 它用在父路由上,告诉它们只要被触发就渲染子路由元素。请记住,所有这些嵌套路由都已预加载到服务器上,因此几乎所有加载状态都可以删除, 听起来有点像 SSR 和 SPA 的混合体。
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/*
此元素将在 URL 为“/messages”时呈现 <Dashboard Messages>,
在“/tasks”呈现 <Dashboard Tasks>,
如果为“/”则呈现 null
*/}
<Outlet />
</div>
);
}
function App() {
return (
<Routes>
<Route path="/" element={<Dashboard />}>
<Route
path="messages"
element={<DashboardMessages />}
/>
<Route path="tasks" element={<DashboardTasks />} />
</Route>
</Routes>
);
}
另一方面,Next.js也确实支持嵌套路由,但它们作为单独的页面工作。 如果想实现与 Remix 类似的效果,可以自定义 _app.js。两者的示例:
- routes/posts/news/index.js => /posts/news
动态路由
Remix、Next.js 都是通过基于动态的 URL 参数渲染不同的内容来工作,同时使用单个路由文件。在这种情况下,两个框架都支持此功能,但使用不同的命名约定。
Next.js 示例
- pages/posts/[postId].js => /posts/some-post-slug
- pages/products/[category]/[productId].js => /products/keyboards/some-keyboard-slug 或 /products/headphones/some-headphone-slug
Next.js 有自己的钩子 useRouter,它将为开发者提供动态参数(postId、类别、productId)的当前值。
import { useRouter } from 'next/router'
function ActiveLink({ children, href }) {
const router = useRouter()
// 使用useRouter示例
const style = {
marginRight: 10,
color: router.asPath === href ? 'red' : 'black',
}
const handleClick = (e) => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
export default ActiveLink
Remix 示例
- /app/routes/posts/$postId.js => /posts/some-post-slug
- /app/routes/products/$category/$productId.js => /products/keyboards/some-keyboard-slug 或 /products/headphones/some-headphone-slug
开发者可以使用 useParam React Router hooks 来访问这些动态参数。
import * as React from 'react';
import { Routes, Route, useParams } from 'react-router-dom';
function ProfilePage() {
// 从URL获取userId参数
let { userId } = useParams();
}
function App() {
return (
<Routes>
<Route path="users">
<Route path=":userId" element={<ProfilePage />} />
<Route path="me" element={...} />
</Route>
</Routes>
);
}
2.5 加载自定义文件的路由
假设需要网站包含 robots.txt 和 sitemap.xml 文件。在 Next.js 和 Remix 中,都可以将它们添加到 /public 目录。 但是 Remix 支持让开发者通过路由系统实现它,只需创建 /app/routes/[sitemap.xml].js 或 /app/routes/[robots.txt].js
2.6 数据加载
Remix、Next.js 框架都是支持服务器端渲染的,因此每个框架都有不同的方式来获取数据和手动调用 API。在 Next.js 下,可以在两个选项之间进行选择,具体取决于要构建的页面类型。
Next.js 方式
- getStaticProps(SSG,如果设置了重新验证间隔,则为 ISR),它在构建时获取数据并将其数据作为页面的组件 props。
export async function getStaticProps({ params }) {
const productList = await getProductList();
return {
props: {
productList,
slug: params.slug,
},
};
}
- getServerSideProps (SSR) : 运行时在服务器端获取数据,并将返回的数据作为页面的组件 props 提供。
export async function getStaticProps({ params }) {
const productList = await getProductList();
return {
props: {
productList,
slug: params.slug,
},
};
}
/**
* 在这里您将拥有自己的 getStaticProps/getServerSideProps 函数
* 取决于您选择的系统。
**/
const PageName = (props) => {
const { productList } = props;
// 在这里你可以呈现请求的数据
return (
<div>
<pre> {JSON.stringify(productList, null, 4)} </pre>
</div>
);
};
Remix 方式
Remix 使用不同的方式加载数据:
- 为开发者提供了一个加载器函数,用于每个路由文件,该文件在服务器端运行。
- 在页面组件本身上,开发者能够使用 useLoaderData Hooks 来收集数据。
/**
* 在这里您将拥有自己的 getStaticProps/getServerSideProps 函数
* 取决于您选择的系统。
**/
export const async loader = ( {params} ) => {
const productList = await getProductList()
return {
productList,
slug: params.slug
}
};
export default function PageName() {
const { productList } = useLoaderData();
// 在这里你可以渲染请求的数据
return (<div>
<pre> {JSON.stringify(productList, null, 4)} </pre>
</div>)
}
2.7 Data Mutations
Next.js 没有内置的方法来执行修改,开发者必须手动进行。Remix 创建了一个表单组件,可以像使用浏览器的 HTML 表单元素一样使用它。 如果开发者不输入任何操作 URL,它将使用与表单路由相同的 URL。
如果方法是 GET,将触发导出函数的加载器,而如果方法是 POST,将触发组件中定义的动作导出函数。
此外,可以使用提供的 useTransition Hooks 根据请求状态管理应用程序的行为,而无需像往常一样手动管理它。
export const loader = async ({ params }) => {
// 每当这个页面收到一个 GET 请求,这个加载器就会被执行
const userData = await getSomeUserData(params.slug);
return {
userData,
// it contains the User Name
};
};
export const action = async ({ request }) => {
// 每当这个页面收到一个 POST 请求,这个加载器就会被执行
const form = await request.formData();
const content = form.get('content');
return redirect('/');
};
export default function App() {
const { name } = useLoaderData();
return (
<div>
<div>{`Hello there ${name}`}</div>
<Form method="POST">
<label htmlFor="content">
Content: <textarea name="content" />
</label>
<input type="submit" value="Add New" />
</Form>
</div>
);
}
2.8 样式处理
Next.js 带有开箱即用的内置 CSS 支持,因此开发者可以直接将其导入到 /pages/_app.js 所需的任何 style.css 文件中。开发者还可以添加其他 CSS 框架,使用一些配置或插件很容易实现。
Remix 更倾向通过提供组件和链接导出(links exported functions)功能来专注于对 Web 标准更加友好的解决方案。
import styles from './styles/app.css';
export function links() {
return [
{ rel: 'stylesheet', href: styles },
{ rel: 'stylesheet', href: 'https://.....' },
];
}
// 链接导出
export default function App() {
return (
<html lang="en">
<head>
<Links />
</head>
<body>
<Outlet />
</body>
</html>
);
}
您可能认为这对于简单地加载样式表来说有点大材小用,但是,如果考虑到可以在每个页面上单独使用它,同时实现按需加载样式、字体,那么一切都是值得的。
同样开发者可以使用其他的 CSS 框架,同时只需很少的配置,比如 Tailwindcss。
2.9 图像优化
Next.js 更胜一筹,因为它提供内置组件 next/image,可以实现图像优化、延迟加载、大小控制和集成加载等。而 Remix 没有提供任何图像优化支持。
import Image from 'next/image'
// 导入next/image
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
const MyImage = (props) => {
return (
<Image
loader={myLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
2.10 搜索引擎优化 SEO
Next.js、Remix 都有自己的内置机制来动态管理每个页面上应该使用哪些元数据,例如关键字、标题、描述等。在 Next.js 中,开发者可以通过将其导入页面/路由来使用其内置组件 next/head。
import Head from 'next/head';
// 导入内置组件next/head
export default function PostPage() {
return (
<div>
<Head>
<title>Post Page Title</title>
<meta name="description" content="Post Page Description" />
</Head>
<p>
All the metadata inside Head component will be injected into the
documents head
</p>
</div>
);
}
Remix 提供了一个 meta 导出函数(meta exported function),可以接收页面请求的数据以及 URL 参数。
export const meta = ({ data, params }) => {
charset: "utf-8",
viewport: "width=device-width,initial-scale=1",
title: `Post | ${data.title}`,
description: data.description,
};
// meta导出函数
export default function PostPage() {
return (
<div>
<p>
All the metadata returned on meta function will be injected into the
documents head
</p>
</div>
)
}
2.11 错误处理
Next.js 使开发者可以在捕获某些错误时自定义页面,例如 400 或 500 错误。 但是除了路由或状态错误之外的任何其他错误都会导致页面中断。
Remix 能让开发者尽情发挥想象力,从而考虑覆盖和自定义想要的每个错误。 Remix 添加了错误边界(Error Boundary),这是一种处理错误的独特概念——在 React v16 中引入。
开发者可以通过在 root.tsx 或者每个页面上使用 CatchBoundary 和 ErrorBoundary 导出函数轻松覆盖这些错误。
// 在这里您将捕获任何受控错误并告诉框架应该显示哪些信息
const CatchBoundary = () => {
let caught = useCatch();
switch (caught.status) {
case 401:
// Document 是一个自定义的 React 组件,它包含 <html>、<head>、<body> 等
return (
<Document title={`${caught.status} ${caught.statusText}`}>
<h1>
{caught.status} {caught.statusText}
</h1>
</Document>
);
case 404:
return <PageNotFoundComponent />;
// 也可以使用 React 组件
default:
throw new Error(
`Unexpected caught response with status: ${caught.status}`
);
}
};
// 在这里,您将获得更通用的建议,说明何时触发了不受控制的错误
const ErrorBoundary = ({ error }) => {
return (
<Document title="Uh-oh!">
<h1>App Error</h1>
<pre>{error.message}</pre>
<p>
将此 UI 替换为您希望用户在您的应用程序抛出时看到的内容未捕获的错误。
</p>
</Document>
);
};
export const loader: LoaderFunction = async ({ params }) => {
const post = await getPost(params.slug);
if (!post) {
throw new Response('Not Found', {
status: 404,
});
}
return json({ post });
};
export default function PostPage() {
const { post } = useLoaderData();
return (
<div>
<pre>{JSON.stringify(post, null, 4)}</pre>
</div>
);
}
2.12 部署
Next.js 很容易部署在任何支持 Node.js 的服务器上。 它还集成了无服务器部署到 Vercel 的功能,Netlify 有自己的适配器,因此可以轻松部署应用程序。
Remix 基于 Web Fetch API 而不是 Node.js 构建,因此可以在 Vercel、Netlify、Architect(Node.js 服务器)以及 Cloudflare 等非 Node.js 环境中运行。同时,Remix 还在开始项目时提供开箱即用的适配器,从而可以直奔主题。
关于 Remix 最重要的事情之一是它不再使用 Webpack。 相反,它使用 Esbuild,它在打包和部署方面速度极快。
2.13 其他差异
- 实时重新加载: Next.js 使用热模块重新加载 (HMR) 并默认启用,而在 Remix 上,开发者必须在 root.tsx 上添加一个组件,以强制应用程序在代码更改时重新加载。
- Remix 支持 Cookie、会话和身份验证,而 Next.js 不支持,必须使用外部库。
- Next.js 内置了对字体和脚本优化的支持,而 Remix 则没有。
- 两者都让您开箱即用地使用 Typescript。
- Remix 无需执行 JavaScript 即可完成其大部分功能,而 Next.js 则不会。
- Remix 可以在嵌套路由中包含独立路由
- 两者都可以使用 Tailwindcss 快速工作,并有自己的指南来实现它。
- Next.js 内置了对国际化路由的支持。
- Next.js 对 AMP 具有开箱即用的支持,而 Remix 则没有。
3. Nextjs 和 Remix:哪个框架适合项目
Nextjs 是一个强大的框架,允许服务器端渲染和静态站点生成,而 Remix 以其速度、易用性和最少的配置而闻名。
项目规模和复杂性
选择框架时,重要的是要考虑项目的规模和复杂性。 Nextjs 是一个更强大的框架,非常适合更大更复杂的项目,而 Remix 更适合更小更简单的项目。
根据开发团队的经验选择合适的框架
如果团队已经熟悉 Nextjs,那么它可能是项目的更好选择。 另一方面,如果您的团队不熟悉 Web 开发,那么 Remix 可能是更好的选择。
长期的扩展性和可维护性
考虑项目的长期可扩展性和可维护性也很重要。 Nextjs 是一个更成熟的框架,拥有更大的社区支持,使其成为长期项目更可靠的选择。下图展示了过去一年Nextjs、Remix的*载下**数据:

数据表明,Nextjs对于Remix具有明显的优势,Github的数据也反映了相同的趋势。

框架选择对性能和 SEO 的影响
最后,重要的是要考虑选择的框架将如何影响网站的性能和 SEO。
Nextjs 以其优化的性能和对 SEO 友好的功能而闻名,而 Remix 更注重速度和开发的便利性。 当然,这两个框架都有自己的优点和缺点,项目的最佳选择将取决于具体需求和目标。
4.本文总结
本文主要和大家介绍Nextjs 和 Remix,以及各自特点,两者如何选择。因为篇幅有限,文章并没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!
参考资料
https://apiumhub.com/tech-blog-barcelona/remix-vs-next-js-which-one-should-you-use/
https://remix.run/docs/en/1.14.1/other-api/react-router
https://juejin.cn/post/7067858524805529637
https://blog.logrocket.com/understanding-routes-route-nesting-remix/
https://blog.logrocket.com/guide-to-remix-react-framework/
https://javascript.plainenglish.io/what-is-remix-why-should-you-use-it-eef76dc9cf2a
https://nextjs.org/docs/api-reference/next/image
https://remix.run/docs/en/1.14.1/other-api/react-router
封面图版权:https://dormoshe.io/daily-news/remix-vs-next-js-a-detailed-comparison-2231.2