Next, Nest, Nuxt… Nust?

December 13, 2022
Written by
Twilion
Reviewed by
Twilion

This blog post is for everyone looking for their new favorite JavaScript backend framework. A few months ago, I was precisely in this place and wanted to learn a new JavaScript backend framework for my side project.

To be honest, I was a bit overwhelmed with all the fancy options to choose from… Next, Nest, Nuxt… - when you hear these names, you probably think the JavaScript community finally ran out of names, and "Nust" will be the next big thing. So the first item on my To-Do list was finding the best framework for my side project.

If you also want to understand the Gatsby, Next.js, Nest, and Nust frameworks better, this post is for you. I’ll explain the similarities and differences between the frameworks, their performance characteristics, real-world popularity, and discuss how to choose the best one for you.

Let’s get started!

Express versus Next.js, Nest, Gatsby, and Nuxt

All of these open-source frameworks are, according to the latest State of JavaScript survey, the most popular backend frameworks. The State of JS survey reveals that Next.js, Gatsby, Nuxt, and Nest are  among the top five. They are behind the undefeated number one Express and still growing. But what are they all about?

Screenshot of the state of js survey

Express has been around almost since the inception of Node.js and is the de-facto standard JavaScript framework. It is comparatively close to the HTTP protocol and offers a minimalist interface, which explains why it’s often used as the basis for many other frameworks. The other frameworks in the top five follow a very different strategy and offer a lot of abstraction to their users. But these abstractions also require a more profound understanding of the frameworks themselves. This post will introduce you to the other contenders in the top 5 of the most used frameworks: Gatsby, Next.js, Nuxt.js, and Nest.

The following two sections cover Web Vitals, such as LP and LCP, and rendering strategies, such as CSR, SSR, and isomorphic apps. You are welcome to jump directly to the frameworks section if you already know what these abbreviations mean.

Performance Metrics for Web Applications

Before we dig into the frameworks, let's think about the requirements for a web application. Almost all projects share one common goal: to deliver relevant, high-quality content to your users. But we also know that even the best content alone doesn't do the job. It also needs to be delivered with acceptable performance and (when talking about public content) needs to be discoverable.

The latter point on discoverability is very straightforward. When you publish content on a public site, you want search engines to index the page, which will then be discoverable in search.

The performance aspect might be a bit harder to define: which performance metric tells you the content is presented timely? I would argue that there is no perfect answer to this question, but there certainly are multiple good metrics that can be combined for a holistic view:

  • Time to First Byte (TTFB)
    TTFB is a metric that measures the time until the first byte of a request is received. This metric can be used to assess the load on the server and the network between the client and server.
  • First Paint (FP)
    The first time any pixel becomes visible to the user. This includes TTFB and the computation the client has to do before the rendering job.
  • First Contentful Paint (FCP)
    First Contentful Paint marks the time the first text or image is painted. Similar to FP, but is a less technical measure and defines the rendering stage that suggests to the user that the page is working.
  • Largest Contentful Paint (LCP)
    Largest Contentful Paint marks the time at which the largest text or image is painted. This measure could also be described as the time until the user sees the expected result.
  • Time to Interactive (TTI)
    Time to interactive is the amount of time it takes for the page to become fully interactive. Users might expect this is the same time as LCP, but this can be different. If TTI and LCP differ, users might assume the website is broken.

Results of a Lighthouse performance measurement in Chrome

These metrics were introduced by the Chrome team and are often also referred to as Web Vitals. The good news is all Blink-based browsers come with development tools to measure these metrics. The bad news is that these metrics are, as of now, not supported by Safari. So you might need to use other browsers for performance tuning, use other metrics in these browsers, or sit in front of the screen with a stopwatch 🙂.

But don't worry. For this post, it doesn't matter which browser you use, as we'll use these metrics only to highlight the differences between various rendering options in the next section.

Rendering Options

On a very high level, all websites work the same. The client sends an initial request to the server that holds the desired information. The server responds with the requested file, which might or might not trigger a cascade of more requests. Eventually, the client has all the needed data and can render a page to display the information visually to the user.

Visualization of the client-server-flow

For static content, i.e., content that looks the same for every user and doesn't require any computations, HTML and CSS files are stored on the server and delivered to the client upon request. The client, in most cases the browser, parses these files and builds the Document Object Model (DOM) based on these instructions.

Unfortunately, large parts of the web consist of dynamic content that needs to differ for each user, possibly even depending on the time or state when the user accesses the web resource. And for these scenarios, computation needs to happen during the requesting process that is visualized above.

When I started programming, Java Servlets were a popular approach to adding dynamic content to websites. Whenever a request reached the server, it ran precompiled Java byte code which printed the HTML response of the request. With the rise of frameworks like AngularJS, Backbone.js, and Knockout in 2010, we saw a shift to Single Page Applications (SPA).

The SPA approach mainly transfers JS and JSON files between the client and server. When executed in the browser, these files render the DOM elements users see. Java Servlet and Single Page Apps commonly execute code to generate the website's markup. However, they differentiate on where and when this code is executed. We can also say they use a different rendering strategy.

Various rendering strategies have advantages and disadvantages based on your requirements. Some might be more relevant to your project than others. The following sections will cover the most common ones we see today.

Client-side Rendering (CSR)

Applications which render individual pages all in the client’s browser are also often referred to as Single Page Applications. This concept is known as CSR, or Client-side Rendering.

The server essentially serves minified JavaScript code executed on the client side to fetch additional information via HTTP requests and renders the DOM elements. For many web developers, this is now the de-facto standard for developing web applications. Popular SPA frameworks are React, Angular, Vue.js, and Svelte.

This approach has the advantage that it scales well as most of the computation happens on the clients, which leads to a fast Time To First Byte. Another advantage is that you can use Web APIs supported by modern browsers such as Geolocation, Clipboard, or Push.

There are disadvantages, though. Since all the rendering happens on the client, this requires additional time once the JavaScript files have been loaded and could lead to a blocked user interface during that time. Hence, metrics like First Paint, First Contentful Paint, Largest Contentful Paint, and Time-to-Interactive require more time.

To compensate for this waiting time, front-end developers often create splash screens to reduce the perceived loading time. Another disadvantage is that most search engines don't execute JavaScript, which leads to problems when you're interested in SEO – as crawlers can have a difficult time understanding page content, it can affect your pages’ discoverability. Of course, there might be projects, such as company-internal apps, where SEO isn't relevant and that aspect doesn't bother you.  

Server-side Rendering (SSR)

Another approach, SSR (or Server-side Rendering) computes the fully rendered HTML before responding to incoming requests, delivering a complete page. This rendering approach is, in many ways, the opposite of client-side rendering. Since search engine crawlers are parsing full web pages, this approach is SEO-friendly and still shows up-to-date content that varies from user to user. Interestingly, First Paint, First Contentful Paint, Largest Contentful Paint, and Time To Interactive are fast and usually very close to each other as the website usually renders all at once.

However, as you guessed, there is a disadvantage here as well. As servers must complete computations before serving the markup, Time To First Byte typically requires more time. This approach is comparably harder to scale as computational load grows with every additional user. Web Pages that look very similar for all users can suffer significantly from this approach, as the same computations with the same results are computed repeatedly.

Static Site Generation (SSG)

SSG, or Static Site Generation, computes page markup during the build time of the web application instead of on-demand. This approach focuses on reducing repeated server-side computation by precomputing static web pages up front. This leads to a longer build time, but eventually saves computation time by serving these precomputed pages.

All metrics (Time To First Byte, First Paint, First Contentful Paint, Largest Contentful Paint, and Time To Interactive) benefit from this approach as it is very similar to static serving. This naturally also leads to low server costs and excellent scalability.

The significant disadvantage is that all pages look the same for every user. This is only desired – or even possible –with some kinds of web applications. Can you imagine a webshop, especially a shopping cart, that looks the same to every user? For some pages, such as API documentation or small blogs, authors can fully leverage this rendering strategy.

But the example of a "static webshop" shows a second disadvantage – every time you change a product or article, you need to rebuild the site! You would need to rebuild the entire application to update a single product description – otherwise, the shop would display outdated information. So, with Static Site Generation it makes sense to modify the strategy a bit and let pages expire or not render them at build-time, and wait until they are requested the first time. These modifications are called Incremental Static Regeneration and Deferred Site Generation, respectively.

Hybrid rendering strategies – universal/isomorphic apps, hydration, and client-side fetching

Pure forms of the previously mentioned strategies move all the computational load to the client, the server, or the build server, respectively. And the side effects are the advantages and disadvantages listed, e.g., SEO-friendly vs. poor SEO support. But it doesn't have to be that way! Why not do some of the computation on the client and some on the server?

For discoverability, it seems reasonable to minimize the amount of client-side rendered page elements. This means we only want to render user-specific elements in the browser. All other page elements – headers, footers, sidebar, or even the full pages, like "About us" – can be rendered and cached on the server to save time.

This approach has a few different names used in various frameworks. Some describe these kinds of apps as universal/isomorphic. Other frameworks call this process (re-)hydration or client-side fetching. Sometimes you also come across the term prerendering, but I avoid it because it isn't clearly defined.

A note about the term prerendering:

This is a term you will often encounter when you research rendering options. At first, the word seems like a great fit, but I try to avoid it because I have yet to find a clear definition.

In a blog post, some Chrome and Shopify engineers mention a "difference between static rendering and prerendering". The Vue.js documentation says Static Site Generation (SSG) is "also referred to as prerendering". Similarly, the Next.js documentation says that Static Generation and Server-side Rendering are "two forms of prerendering". The Jamstack documentation follows a similar idea, but calls anything "which can be served directly from a CDN" prerendering. This includes Static Site Generation, Server-side Rendering, and the hybrid approach.

I share this opinion, too: Pre-rendering is everything that is not exclusively rendered on the client side. But I don't want to contribute to the confusion and try to avoid the term (at least for now).

Independent of the name, this strategy has a fast Time To First Byte, First Paint, and First Contentful Paint that is also SEO-friendly. Only the Largest Contentful Paint and the Time To Interactive usually show a disadvantage. But overall, these metrics still perform better versus Single Page Apps solely rendered on the client.

There's not a real disadvantage concerning the Web Vital metrics of this approach, but there is a pragmatic one. It comes with additional complexity to define when to render which page elements where.

Multiple rendering strategies

You see, there are many rendering strategies to choose from. And truth be told, these classifications are more theoretical than anything else. In real life, one website can use multiple strategies for different subpages. To stick with the example of a webshop, the catalog and product pages must have a fast loading time and be SEO-optimized simultaneously. That sounds like a job for Static Site Generation. But there is also the need for some user-specific pages where discoverability isn’t a factor, such as the shopping cart. It makes sense to leverage a hybrid rendering strategy for these subpages. The layout of the cart page, which is the same for all users, can be rendered on the server, and the items of the cart can be rendered via client-side fetching.

When you implement a web app partially rendered on the client and the server, how do you decide which code needs to run on which platform or how the code is minified and split into individual chunks? This can get very complicated fast. But thankfully, there are frameworks that can help you with this task.

Frameworks

Gatsby

Gatsby was initially conceptualized in 2015 as a framework for Static Site Generation of React web apps. Over time, it added a few other rendering options, such as Deferred Site Generation (on the first request), Server-Side Rendering (which you should only use in some cases), and client-side fetching.

You can source data from multiple systems, such as database management or headless content management systems, to build the React sites you need. But one of the technologies that Gatsby is most strongly related to is GraphQL. It includes its own GraphQL server to serve metadata about the server and from connected sources.

A special highlight of this framework is the size of its open-source community, which has built a large Plugin Library that allows you to stand on the shoulders of giants.

This is how a Gatsby 404 page could look:

import * as React from "react"
import { Link } from "gatsby"
import { StaticImage } from "gatsby-plugin-image";


const pageStyles = {
 color: "#232129",
 padding: "96px",
 fontFamily: "-apple-system, Roboto, sans-serif, serif",
}
const headingStyles = {
 marginTop: 0,
 marginBottom: 64,
 maxWidth: 320,
}

const paragraphStyles = {
 marginBottom: 48,
}
const codeStyles = {
 color: "#8A6534",
 padding: 4,
 backgroundColor: "#FFF4DB",
 fontSize: "1.25rem",
 borderRadius: 4,
}

const NotFoundPage = () => {
 return (
   <main style={pageStyles}>
     <h1 style={headingStyles}>Page not found</h1>
     <StaticImage alt="Clifford, a reddish-brown pitbull"
       src="https://pbs.twimg.com/media/E1oMV3QVgAIr1NT?format=jpg&name=large"
     />

     <p style={paragraphStyles}>
       Sorry 😔, we couldn't find what you were looking for.
       <br />
       {process.env.NODE_ENV === "development" ? (
         <>
           <br />
           Try creating a page in <code style={codeStyles}>src/pages/</code>.
           <br />
         </>
       ) : null}
       <br />
       <Link to="/">Go home</Link>.
     </p>
   </main>
 )
}

export default NotFoundPage

With its supported rendering options, Gatsby is well-equipped to implement Jamstack Sites. Here's a great resource on Jamstack that explains the benefit of applications that decouple the web experience layer from data and business logic. Sites built with Gatsby are often used for "content consumption" that offers a great user experience, such as the web presence of companies.

The framework doesn't support custom backend logic, so you should look for another framework, such as Next.js, if that's what you need.

Next.js

The Next.js framework, published initially in 2016, is in many ways similar to Gatsby, with the addition of a few edge computing features. These features include Edge Functions, the serverless execution of custom code, and Edge Middleware that can be used to redirect certain requests to other domains.  This allows you to run your serverless Next.js app globally without worrying about server management. Next.js supports Static Site Generation, Server-side Rendering, Incremental Static Regeneration, and client-side data fetching.

A home page in Next.js

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'

export default function Home() {
 return (
   <div className={styles.container}>
     <Head>
       <title>Create Next App</title>
       <meta name="description" content="Generated by create next app" />
       <link rel="icon" href="/favicon.ico" />
     </Head>

     <main className={styles.main}>
       <h1 className={styles.title}>
         Welcome to <a href="https://nextjs.org">Next.js!</a>
       </h1>

       <p className={styles.description}>
         Get started by editing{' '}
         <code className={styles.code}>pages/index.js</code>
       </p>
     </main>

     <footer className={styles.footer}>
       <a
         href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
         target="_blank"
         rel="noopener noreferrer"
       >
         Powered by{' '}
         <span className={styles.logo}>
           <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
         </span>
       </a>
     </footer>
   </div>
 )
}

An Edge function:

const twilio = require("twilio");

export default function handler(req, res) {
 const twiml = new twilio.twiml.MessagingResponse();
 twiml.message("Hi, I'm a TwiML response");
 res.setHeader("Content-Type", "text/xml");
 res.status(200).send(twiml.toString());
}

A unique feature of Next.js is its huge popularity, big community, and widespread usage. The framework allows you to host it on your own infrastructure, but you can also deploy it to multiple edge networks to outsource server administration work.

On the other hand, this serverless nature can also be a downside. There is no notion of state in the framework; therefore, you cannot easily store state without manually adding a database.

NuxtJS

NuxtJS, published initially in 2016,  differs from the previous frameworks in multiple ways. The most obvious difference is that you don’t build with React, but on another popular UI framework: Vue.js. But that does not mean that Nuxt is a port of Next.js. Nuxt is conceptually very different and offers all rendering options - even Client-side Rendering. So this framework can also be used for Progressive Web Apps.

On top of that, it comes with a rich wizard that asks questions for different configurations, testing frameworks, building frameworks, and component packages. So it can also be seen as a framework for frameworks. Nuxt also comes with state storage functionality: the Vuex Store.

A Nuxt page definition

<template>
 <div>
   <h1>Hello Nuxters! 👋</h1>
   <p>
     This page is rendered on the <strong>{{ rendering }}</strong>
   </p>
   <p v-if="rendering === 'server'">
     First load or hard refresh is done on server side.
   </p>
   <p v-if="rendering === 'client'">Navigation is done on client side.</p>
   <ul>
     <li>Refresh the page for server side rendering.</li>
     <li>Click the links to see client side rendering.</li>
   </ul>

   <NuxtLink to="/about">About Page</NuxtLink>
 </div>
</template>
<script>
export default {
 asyncData() {
   return {
     rendering: process.server ? 'server' : 'client'
   }
 }
}
</script>

To avoid confusion, it's worth highlighting that Nuxt offers many options for developing websites. But that doesn't mean that it's a framework for creating your own server applications that need to handle Object Relational Mappers or protocols besides HTTP.

There’s a great memory trick to distinguish these similar sounding frameworks: Next.js uses React and Nuxt uses Vue.js.

Nest

Nest, the Angular-inspired framework from 2018, is the real outlier in this series. While all other frameworks implement various rendering strategies, Nest lives in a different sphere away from front-ends. This framework for server-side applications is more similar to the famous Spring framework and can be used to implement microservices. It supports virtually all popular database management systems via object-relational mappers and many additional protocols such as WebSockets, GraphQL, and MQTT.

Under the hood, Nest uses Express but also provides compatibility with a wide range of other libraries, like, e.g., Fastify, allowing for convenient use of third-party plugins. But my favorite feature is the neat code-reuse mechanism via modules. Modules allow you to externalize features that you can reuse in multiple parts of your application or share with the entire Nest community as libraries.

Module usage in a Nest application:


import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from "@nestjs/config";

@Module({
 imports: [ConfigModule.forRoot()],
 controllers: [AppController],
 providers: [AppService],
})
export class AppModule {}

It's essential to remember that this framework is not related to any front-end framework by default. That means you manually need to integrate the dist folder with your front-end project in this project directory (or better: automate this in the build script). These kinds of applications often need to handle more complex computations and therefore have a higher server load and can be harder to scale.

Real-world Popularity

The usage/awareness ranking of the State of JS survey comes with a selection bias and does not necessarily reflect the actual situation. Hence, I looked at some more quantitative data that indicate the developer experience of these frameworks.

I inspected the download numbers from npm and some community stats from GitHub and Stack Overflow. This data is accurate as of December 2022.

Next.jsGatsbyNuxtJSNest
Usage/Awareness#2#3#4#5
Weekly downloads3.5m412k485k1.9m
GH Stars97.7k53.9k10.9k *52.8k
GH Forks21.6k10.5k1.1k *6.3k
GH Contributors2.4k3.9k340 *350
SO Questions (answered)25k (12.3k)5.6k (3.7k)10.9k (5.7k)9.8k (5.1k)

* Started a new repository for version 3

 

Across almost all these metrics, we see Next.js’s leadership. It has the most downloads, GitHub stars, and forks, and the most extensive knowledge base on Stack Overflow. Surprisingly, Gatsby has the most GitHub contributions, which shows how big its rich ecosystem is. At first, it also seems surprising that Nest has so many weekly downloads compared to Gatsby and Nuxt, even if the other stats are very similar. A possible explanation might be that Nest is the only listed framework that can be used to implement backend servers and is, therefore, without competition. Even though the other frameworks are not freely interchangeable, they all can be used for server-rendered HTML pages and might cannibalize themselves to some degree.

How to Choose

Let's revisit the start of this post now that we know more about these frameworks. Which framework would be best suited for a side project? To answer this question, you need to ask yourself the following questions:

  • Do you want to build a full-stack app or not?
  • If so, do you want more freedom and flexibility or prefer out-of-the-box modules, if available in the first place, that you can plug in to get the desired result?
  • If the app doesn't require full-stack development, which rendering strategy do you prefer based on your needs for performance and SEO-friendliness?

This decision tree includes these questions and will help you focus on the frameworks you might want to consider:

Decision tree for the frameworks

 

My recommendations, and what’s next

If you’d like to learn something new, I’d recommend checking out Next.js. It’s great if you want to build a website that leverages React and has an informative character without a lot of interactivity, such as a company website, a blog, or a landing page for a product. If you want to build such a site but prefer Vue.js, then Nuxt will be your friend.

It’s also ok to build a SPA, without a backend framework, with React or Vue.js if this fits your needs. When you want to build a server application, on the other hand, it makes sense to evaluate whether you want to build with established modules that offer a plug-and-play mechanism (Nest) or whether you want to be able to build everything on your own with Express.

If you are planning to develop with any of these frameworks and you have open questions, feel free to contact me via any of the following channels.