大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
今天给大家带来的主题是Nextjs、Remix,各自特点、全方位差异比较。话不多说,直接开始!
当想要基于 React 创建一个新的 Web 项目时,有许多不同的框架可以选择。 作为一名前端开发人员,您会发现自己很难知道应该选择哪一个框架,或者哪一个框架最适合开发需求。
最常用的框架之一是 Next.js,Netflix、Twitch 或 Uber 等公司都已经在生产项目中使用它, Next.js 被认为是增长最快的 React 框架之一。其他框架很难与 Next.js 媲美,因为它涵盖了三种不同的页面渲染策略。但自 2021 年 11 月以来,前端又多了一个全新的、强大的备选框架,称为 Remix。本篇文章将重点聚焦在 Remix、Next.js 框架的比较上。
React 可以制作单页应用程序 (SPA),其仅使用一个 HTML 文件来呈现所有网页,路由由客户端控制。
简而言之,浏览器负责管理应加载哪个 JavaScript 文件以渲染当前页面,这就是客户端渲染 ,即 CSR。但是,CSR 也存在诸多限制:
由于 CSR 的三个核心问题,Next.js 和 Remix 出现了, 它们可以在发送客户端请求之前在服务器端渲染内容,从而最大限度的缩短页面白屏时间,提升用户体验。
除了 CSR ,还有诸如 SSR 、 SSG 、 ISR 等其他页面渲染策略。Next.js 提供了一个强大的配置选项,支持 3 个不同的开箱即用的渲染方式,而 Remix 完全依赖于 SSR(至少目前)。
SSR,即 Server-Side Rendering。当一个页面被请求时,它在发送到客户端之前在服务器上完成渲染。 对于那些拥有大量动态数据和内容的网站来说,这是一个很好的选择。
SSG,即 Static Site Generation ,该策略在构建期间按路由生成所有页面内容。 SSG 只会将当前被请求的页面发送给客户端,这构建了一个典型的静态网站。
对于那些几乎没有动态数据且页面不多的静态网站来说,SSG是一个合适的选择。 但是,需要注意的是,数据的更改都会触发所有页面的重新生成。
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 中,一切都需要开发者借助中间件来实现它。
Next.js 和 Remix 框架都使用基于文件的路由系统来渲染页面:
在 /app/routes 中添加每个路由,在其中创建的每个文件或文件夹都将被视为路由。可以在 /app/root.jsx 中可以找到一个文件,它是“根路由”,是整个应用程序的布局。
可以在 /pages 中添加路由,它的工作方式与 Remix 完全相同。 pages/_app.js 和 pages/_document.js 可有可无。 其中 App 用于初始化页面,Document 包含一系列标签。
Next.js 默认使用它们,无需额外配置。 但是,如果开发者需要自定义,可以创建这两个文件,以这种方式覆盖默认文件。
Remix 和 Next.js 都使用相同的系统来呈现容器路由(container route)。
Next.js 示例:
Remix 示例:
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。两者的示例:
Remix、Next.js 都是通过基于动态的 URL 参数渲染不同的内容来工作,同时使用单个路由文件。在这种情况下,两个框架都支持此功能,但使用不同的命名约定。
Next.js 示例
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 示例
开发者可以使用 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> );}
假设需要网站包含 robots.txt 和 sitemap.xml 文件。在 Next.js 和 Remix 中,都可以将它们添加到 /public 目录。 但是 Remix 支持让开发者通过路由系统实现它,只需创建 /app/routes/[sitemap.xml].js 或 /app/routes/[robots.txt].js
Remix、Next.js 框架都是支持服务器端渲染的,因此每个框架都有不同的方式来获取数据和手动调用 API。在 Next.js 下,可以在两个选项之间进行选择,具体取决于要构建的页面类型。
export async function getStaticProps({ params }) { const productList = await getProductList(); return { props: { productList, slug: params.slug, }, };}
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 使用不同的方式加载数据:
/** * 在这里您将拥有自己的 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>)}
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> );}
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。
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} /> )}
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> )}
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> );}
Next.js 很容易部署在任何支持 Node.js 的服务器上。 它还集成了无服务器部署到 Vercel 的功能,Netlify 有自己的适配器,因此可以轻松部署应用程序。
Remix 基于 Web Fetch API 而不是 Node.js 构建,因此可以在 Vercel、Netlify、Architect(Node.js 服务器)以及 Cloudflare 等非 Node.js 环境中运行。同时,Remix 还在开始项目时提供开箱即用的适配器,从而可以直奔主题。
关于 Remix 最重要的事情之一是它不再使用 Webpack。 相反,它使用 Esbuild,它在打包和部署方面速度极快。
Nextjs 是一个强大的框架,允许服务器端渲染和静态站点生成,而 Remix 以其速度、易用性和最少的配置而闻名。
选择框架时,重要的是要考虑项目的规模和复杂性。 Nextjs 是一个更强大的框架,非常适合更大更复杂的项目,而 Remix 更适合更小更简单的项目。
如果团队已经熟悉 Nextjs,那么它可能是项目的更好选择。 另一方面,如果您的团队不熟悉 Web 开发,那么 Remix 可能是更好的选择。
考虑项目的长期可扩展性和可维护性也很重要。 Nextjs 是一个更成熟的框架,拥有更大的社区支持,使其成为长期项目更可靠的选择。下图展示了过去一年Nextjs、Remix的下载数据:
数据表明,Nextjs对于Remix具有明显的优势,Github的数据也反映了相同的趋势。
最后,重要的是要考虑选择的框架将如何影响网站的性能和 SEO。
Nextjs 以其优化的性能和对 SEO 友好的功能而闻名,而 Remix 更注重速度和开发的便利性。 当然,这两个框架都有自己的优点和缺点,项目的最佳选择将取决于具体需求和目标。
本文主要和大家介绍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