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

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

图片来自:https://dormoshe.io/daily-news/remix-vs-next-js-a-detailed-comparison-2231.2
最常用的框架之一是 Next.js?1776568152.459 ,Netflix、Twitch 或 Uber 等公司都已经在生产项目中使用它, Next.js?1776568152.459 被认为是增长最快的 React 框架之一。其他框架很难与 Next.js?1776568152.459 媲美,因为它涵盖了三种不同的页面渲染策略。但自 2021 年 11 月以来,前端又多了一个全新的、强大的备选框架,称为 Remix。本篇文章将重点聚焦在 Remix、Next.js?1776568152.459 框架的比较上。
1.为何需要 Next.js?1776568152.459 或 Remix 而不是纯 React
React 可以制作单页应用程序 (SPA),其仅使用一个 HTML 文件来呈现所有网页,路由由客户端控制。
- 当客户端发出初始请求时,浏览器会立即收到一个包含所有应用程序的 HTML 页面,没有预渲染内容
- 当用户在应用导航时,JavaScript 仅替换那些与请求路由相关的组件和内容,但 HTML 文件保持保持不变
简而言之,浏览器负责管理应加载哪个 JavaScript 文件以渲染当前页面,这就是客户端渲染 ,即 CSR。但是,CSR 也存在诸多限制:
- Slow Initial Render:当用户第一次访问页面时,需要很长时间才能加载完成。 这意味着较长的空白页面,直到 JavaScript 加载并渲染所有内容。
- SEO:因为初始时只有一个 HTML 页面,所以很难让搜索引擎对内容进行索引。
- 缓存问题:HTML 结构无法缓存,因为它在第一次渲染时不可用。
由于 CSR 的三个核心问题,Next.js?1776568152.459 和 Remix 出现了, 它们可以在发送客户端请求之前在服务器端渲染内容,从而最大限度的缩短页面白屏时间,提升用户体验。
2.SSR vs SSG vs ISR
除了 CSR ,还有诸如 SSR 、 SSG 、 ISR 等其他页面渲染策略。Next.js?1776568152.459 提供了一个强大的配置选项,支持 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?1776568152.459 引入了这个概念,其是 SSR 和 SSG 的混合体。它类似于 SSG,但开发者可以设置一个间隔时间,用于控制何时应该重新生成页面。 适用于那些具有动态内容的静态站点。
Next.js?1776568152.459 的优势在于允许开发者在每个页面上的 3 个选项之间灵活切换、选择。所以也许你会问:“如果 Remix 只适用于 SSR,为何要用它替换 Next.js?1776568152.459 ”。 答案很简单,如今的应用程序和网站往往具有动态数据,并且很少有适合 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?1776568152.459 中,一切都需要开发者借助中间件来实现它。
2.4 Routing 路由
Next.js?1776568152.459 和 Remix 框架都使用基于文件的路由系统来渲染页面:
- 每个页面都保留在不同的文件中
- 每个页面都与基于其文件名的路由相关联
- 每个页面都会渲染一个 React 组件。
Remix 中路由
在 /app/routes 中添加每个路由,在其中创建的每个文件或文件夹都将被视为路由。可以在 /app/root.js?1776568152.459 x 中可以找到一个文件,它是“根路由”,是整个应用程序的布局。
Next.js?1776568152.459 中路由
可以在 /pages 中添加路由,它的工作方式与 Remix 完全相同。 pages/_app.js?1776568152.459 和 pages/_document.js?1776568152.459 可有可无。 其中 App 用于初始化页面,Document 包含一系列标签。
Next.js?1776568152.459 默认使用它们,无需额外配置。 但是,如果开发者需要自定义,可以创建这两个文件,以这种方式覆盖默认文件。
Index 路由
Remix 和 Next.js?1776568152.459 都使用相同的系统来呈现容器路由(container route)。
Next.js?1776568152.459 示例:
- pages/index.js?1776568152.459 => /(根页面)
- pages/posts/index.js?1776568152.459 或 pages/posts.js?1776568152.459 => /posts
Remix 示例:
- routes/index.js?1776568152.459 => /(根页面)
- routes/posts/index.js?1776568152.459 或 routes/posts.js?1776568152.459 => /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?1776568152.459 也确实支持嵌套路由,但它们作为单独的页面工作。 如果想实现与 Remix 类似的效果,可以自定义 _app.js?1776568152.459 。两者的示例:
- routes/posts/news/index.js?1776568152.459 => /posts/news
动态路由
Remix、Next.js?1776568152.459 都是通过基于动态的 URL 参数渲染不同的内容来工作,同时使用单个路由文件。在这种情况下,两个框架都支持此功能,但使用不同的命名约定。
Next.js?1776568152.459 示例
- pages/posts/[postId].js?1776568152.459 => /posts/some-post-slug
- pages/products/[category]/[productId].js?1776568152.459 => /products/keyboards/some-keyboard-slug 或 /products/headphones/some-headphone-slug
Next.js?1776568152.459 有自己的钩子 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?1776568152.459 => /posts/some-post-slug
- /app/routes/products/$category/$productId.js?1776568152.459 => /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?1776568152.459 和 Remix 中,都可以将它们添加到 /public 目录。 但是 Remix 支持让开发者通过路由系统实现它,只需创建 /app/routes/[sitemap.xml].js?1776568152.459 或 /app/routes/[robots.txt].js?1776568152.459
2.6 数据加载
Remix、Next.js?1776568152.459 框架都是支持服务器端渲染的,因此每个框架都有不同的方式来获取数据和手动调用 API。在 Next.js?1776568152.459 下,可以在两个选项之间进行选择,具体取决于要构建的页面类型。
Next.js?1776568152.459 方式
- 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?1776568152.459 没有内置的方法来执行修改,开发者必须手动进行。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?1776568152.459 带有开箱即用的内置 CSS 支持,因此开发者可以直接将其导入到 /pages/_app.js?1776568152.459 所需的任何 style.css?1776568152.459 文件中。开发者还可以添加其他 CSS 框架,使用一些配置或插件很容易实现。
Remix 更倾向通过提供组件和链接导出(links exported functions)功能来专注于对 Web 标准更加友好的解决方案。
import styles from './styles/app.css?1776568152.459 ';
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?1776568152.459 更胜一筹,因为它提供内置组件 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?1776568152.459 、Remix 都有自己的内置机制来动态管理每个页面上应该使用哪些元数据,例如关键字、标题、描述等。在 Next.js?1776568152.459 中,开发者可以通过将其导入页面/路由来使用其内置组件 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?1776568152.459 使开发者可以在捕获某些错误时自定义页面,例如 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?1776568152.459 很容易部署在任何支持 Node.js?1776568152.459 的服务器上。 它还集成了无服务器部署到 Vercel 的功能,Netlify 有自己的适配器,因此可以轻松部署应用程序。
Remix 基于 Web Fetch API 而不是 Node.js?1776568152.459 构建,因此可以在 Vercel、Netlify、Architect(Node.js?1776568152.459 服务器)以及 Cloudflare 等非 Node.js?1776568152.459 环境中运行。同时,Remix 还在开始项目时提供开箱即用的适配器,从而可以直奔主题。
关于 Remix 最重要的事情之一是它不再使用 Webpack。 相反,它使用 Esbuild,它在打包和部署方面速度极快。
2.13 其他差异
- 实时重新加载: Next.js?1776568152.459 使用热模块重新加载 (HMR) 并默认启用,而在 Remix 上,开发者必须在 root.tsx 上添加一个组件,以强制应用程序在代码更改时重新加载。
- Remix 支持 Cookie、会话和身份验证,而 Next.js?1776568152.459 不支持,必须使用外部库。
- Next.js?1776568152.459 内置了对字体和脚本优化的支持,而 Remix 则没有。
- 两者都让您开箱即用地使用 Typescript。
- Remix 无需执行 JavaScript 即可完成其大部分功能,而 Next.js?1776568152.459 则不会。
- Remix 可以在嵌套路由中包含独立路由
- 两者都可以使用 Tailwindcss 快速工作,并有自己的指南来实现它。
- Next.js?1776568152.459 内置了对国际化路由的支持。
- Next.js?1776568152.459 对 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