前端技术对比:Next.js与Remix,哪个更胜一筹?

发表时间: 2023-03-15 06:16

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

高级前端进阶

今天给大家带来的主题是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/imageconst 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/headexport 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