DEV Community

Viktor Lázár
Viktor Lázár

Posted on

@lazarv/react-server vs Next.js

In this article we will compare @lazarv/react-server to Next.js as these React meta-frameworks are very close because both are based on the new React 19 server-side rendering features.

We will investigate both frameworks on their approach and architectural choices.

What is a meta-framework?

We can consider a solution to be a meta-framework when the following features are supported and the framework is using a specific library as it's base. Which is React in our case.

  • File-based routing
  • Static generation
  • Hybrid rendering
  • Data fetching
  • Isomorphic component trees

The React team suggests to use a framework for future React development. Next.js, Remix, Gatsby and Expo are their suggestion, while mentioning Next.js App Router as the bleeding-edge framework. If you really want to use React without a framework, you can still use a bundler like Vite or Parcel.

Next.js

Originally released in 2016 and nowadays it's the go-to meta-framework for React. There are some developers from the Next.js team who are also involved in the development of React itself. This means that Next.js is following the architectural choices the React team considers the best for React. This could be true for certain use cases, but still it's debatable. One major issue with Next.js is that it's optimized for the Vercel platform in every single way. While you can run your Next.js app in every environment which supports node.js, making this choice removes or at least diminishes a lot of nice and valuable features from your Next.js app, which could use Vercel platform features to implement these app features in the most optimal way. You can think of image optimalization, ISR, caching, etc.

Next.js is using Webpack under the hood to bundle your app. While there's experimental support for Turbopack and support for using SWC and Lightning CSS as the compiler. Next.js is still using CommonJS (with some support for ES modules), while the node.js community is mostly trying to switch over to native ES modules and CommonJS is considered legacy now, with some exceptions. Support will remain for years for sure, but there are already npm packages which only support ES modules since a specific version.

@lazarv/react-server

In contrast to Next.js, @lazarv/react-server is using Vite for it's development server and as using Vite, it's bundling for production using Rollup. With this choice it's fully supporting native ES modules. This framework is not favoring any cloud providers like Vercel, but supports deployment to Vercel. As the framework is more of a wrapper around React 19 server-side rendering features without any strict choices on how to implement specific features, it's easier to shape your app the way you want it to work and behave. The only exception is that it will use Vite. Based on that, it's also easier to run apps literally anywhere where you can run a node.js service. The framework also supports cluster mode, which creates a node.js cluster for your app which is beneficial if you run your app in an environment where you want to fully utilize the available resources.

Shared features

As both @lazarv/react-server and Next.js is using React at it's core, both share a lot of features which are provided by React and are not Next.js nor @lazarv/react-server features directly.

React Server Components is a hot topic in the community and it's a very versatile tool. It's surely is the next most different new feature in React 19 since hooks. RSCs are rendered only on the server-side and have no client code so you can't re-render these on the client.

To open a door from RSCs into the client code, you need to create client components. This type of components are basically the same as you already used to in a React server-side rendered app. These are also rendered on the server, but getting hydrated on the client and these are able to re-render on the client based on their state. You can mix RSCs and client components in every way. RSCs can contain client components and client components can also contain RSCs. It's absolutely up to the developer how the app is structured and which part of the app is using RSCs and which part is created using client components and are also running in the browser.

Using RSCs you are able to load initial data into your client components. To use mutation, you can use server actions. This feature is like a built-in RPC into React and the React meta-frameworks. You can call a server action using HTML form or button elements or by passing the server action as a reference to the client component and calling it like a normal async JavaScript function.

While with Next.js you have to use it's built-in file-based router named the App Router, when you're using @lazarv/react-server it's only up to you if you want to use the optional file-based router or you want to implement your application using another solution. If you still need the file-based router, both framework's solution for the router is very similar and if you used Next.js App Router, it will be easy to do the same using @lazarv/react-server-router. It's just optional. Both framework's routing solution supports middlewares and API routes.

Getting started

All frameworks provide a project creation command line tool for developers to help getting started with it. Next.js has npx create-next-app, Remix has npx create-remix, etc. All of these CLI tools create a new project, initializing files required to run your app using that framework and it's also installing required dependencies. These are mostly marketed as a 5min task. Which is fair. These tools are easy to use and it's really just a few minutes.

@lazarv/react-server is different. You don't need any tools to initialize your app. You just need @lazarv/react-server. After installing it as a dependency or just using npx to run the framework's CLI, you're done. It brings back the most simple developer experience ever: node server.js. You already have a JSX file with a component? Just run npx @lazarv/react-server ./App.jsx. You don't need to install React. It would be pointless and Next.js is also including it's own specific version of React as for all React 19 and experimental React features to work properly, you need a specific version of React. @lazarv/react-server installs it's own React too. So running your first app with @lazarv/react-server takes a few seconds. Nothing more. But infinitely expandable.

Some numbers

Next.js can be slow. This might not feels that much until you experienced something better and @lazarv/react-server wins by a lot in this. You can experience the same when you migrated your old Create React App project to Vite.

Our benchmark app might feel weak as it's only the most simple React app possible, but measuring the difference even in this most minimal use case means that Next.js will always lag behind because it's heavy-weight architecture. This is not achieved by using a faster bundler, only with a more suiting architecture.

Our tiny app looks like this:

export default function Page() {
  return (
    <html lang="en">
      <body>
        <h1>Hello World!</h1>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

For Next.js a mock layout file also needs to be created, just rendering children and doing nothing else.

The results seem devastating:

Next.js @lazarv/react-server
startup ~800ms ~150ms
compiling ~600ms N/A
page load ~150ms ~30ms
build ~3.5s ~200ms
start ~100ms ~100ms
rps ~9k ~21k

Using the cluster mode available for the production runtime of @lazarv/react-server you can achieve even more blazing-fast performance even without caching. Using an 8 core M3 Macbook Air it's hitting a whopping ~50k requests per second using all the available CPU cores, so running 8 workers!

Results

Let's get a look on each type of result and the reason behind the numbers!

startup

This is the startup time for the development server. Next.js startup is more than 4x. Next.js is much more heavy and core codebase of Next.js is way too much for an app like this, while @lazarv/react-server is a light-weight server with being a wrapper around Vite and React where no unnecessary code is run on startup.

compiling

Next.js needs to create a bundle for your app. But using Vite, @lazarv/react-server eliminates this part by not creating any bundle during development. The underlying bundler in case of Vite doesn't need to collect and compile a bundle before you could use your app. This is an essential architectural choice with Vite. Learn more about this at Why Vite?.

page load

Next.js loads roughly twice as much client side code when running your app and the client-side router is much more complex. Performance for page load is about 5x with @lazarv/react-server.

build

When you are finished implementing your app and ready to deploy it to somewhere, you need to build your app for production use. Again, Next.js is lagging behind. Even when using a powerhouse developer machine, it takes seconds to build your Next.js app for production! While the Rollup based build in @lazarv/react-server is just a blink of an eye compared to the Next.js build. The difference is most prominent in this case between the two frameworks. @lazarv/react-server wins by a whopping 18x performance gain!

start

This is the startup time for the production server of the framework and also the only category which the frameworks are getting a tie.

Conclusion

Do you really need to work with Next.js all the time and in all use case? Surely not. Next.js might be still excellent and awesome for a ton of apps. But there are also way to many apps that are using Next.js even when they should not. By using @lazarv/react-server developers don't need to use a heavier framework like Next.js when the app is not needing all features or you will run your app on your own. When you would use Vite for a single page application, but might be a bit jealous on the SSR niceties React 19 provides.

Top comments (0)