Remix vs Next.js which one should you use?


Nowadays we have many different framework options when we want to create a new web project based on React. As a developer, you can find yourself struggling to know which one should you choose or which one would best suit your needs.

One of the most used frameworks you may know is Next.js, commonly used by companies like Netflix, Twitch, or Uber. It is considered one of the fastest-growing React frameworks.

It has been difficult for others to compete with Next.js as it covers three different page rendering strategies, but since November 2021 it seems we have a new, fresh and powerful framework called Remix.

Sorry Gatsby I didn’t forget you and I love the way you work when generating static sites

Why do you need Next.js or Remix instead of plain React

Maybe you don’t, it depends. The point is to understand how React applications work, which problems you could find and how frameworks such as Next.js or Remix work to solve them. 

The point about React is that you can make Single Page Apps (SPA), where only a single HTML file is used to render all web pages and the routing stays on the client-side. But what does this mean exactly?

  • When the initial request is made, the browser receives an HTML box page that contains all the applications at once. 
  • No pre-rendered content
  • When user navigation triggers, JavaScript replaces only those components and content related to the requested route, but the HTML file stays the same.

In short, the browser is the one managing which JavaScript file should be loaded to render the current page. In other words Client-Side Rendering (CSR)

CSR can be very fancy and helpful to create applications that don’t need to care about SEO, caching or slow initial render.

  • SSlow Initial Render – When a user first lands it could take a long time to load up the first contentful page. This means to let them get a blank page until JavaScript loads and everything is rendered.
  • SEO – As it will have only a single HTML page the content it’s quite difficult to get the content indexed by search engines and social media bots.
  • Caching problems – the HTML structure cannot be cached as it is not available on the first render.

At this point you may think that SPAs are the devil, but think about all those internal applications that so many companies use, or the application products they sell. 

Next.js or Remix appears when you want to take advantage of how SPAs work without losing the three points I mentioned before. The strong point is that a website can be rendered on the server side before sending it to the client.

SSR vs SSG vs ISR

I have already explained what does mean and how CSR works, now it’s my turn to talk about other fancy page rendering strategies. 

Next.js is a powerful option as it comes with 3 different options out of the box while Remix relies fully on SSR (at this moment). But how do these strategies work exactly?

Server-Side Rendering (SSR)

When a page is requested, it is fully rendered on the server before it is sent to the client. Being a great option for those websites with a lot of dynamic data and content.

Static Site Generation (SSG)

This strategy generates all pages by route during build time. Only the requested page is sent to the client when prompted. In other words, this constructs a typical static website. 

It can be a suitable option for those static websites with little to no dynamic data and not so many pages. Each change in the data will trigger a regeneration of all the pages.

Incremental Static Regeneration (ISR)

This concept has been offered by Next.js and is kind of a hybrid of SSR and SSG. 

It is like SSG but you can set an interval time to let know when your pages should be regenerated. Suitable for those static sites with dynamic content.

The strong point about Next.js is that it lets you switch between those 3 options on each page, so you can have each of them following different strategies.

So maybe you ask yourself “why should I replace Next.js with Remix if it only works with SSR”. And the answer is pretty simple, nowadays applications and websites tend to have dynamic data and there are few examples that would suit an SSG or ISR scenario. 

Taking this into account, Remix provides a lower-level API. For example, it exposes the Request object so you can easily modify headers before rendering the page, while in Next.js you would need middleware to achieve it.

Routing

Both frameworks use a file-based routing system to render the pages. 

  • Each page remains in a different file.
  • Each page is associated with a route based on its file name.
  • Each page, in the end, renders a React Component.

Routing in Remix

You will add each route in /app/routes and every file or folder created inside it will be treated as a route.

Also, you will find a file in /app/root.jsx which is the “root route” and is the layout for the entire application. It contains the , and tags, as well as other Remix related stuff.

Routing in Next.js

You will add each route in /pages and it works exactly in the same way as Remix.

nextjs routing 1

In this case, you may, or not, have a pages/_app.js and a pages/_document.js. Where App is used to initialize pages and Document contains the, and tags.

Next.js uses them by default with no needed extra configuration. But, in case you need to customize them you can create those two files, overriding this way the default ones.

Index routes

Both of them use the same system to render a container route.

Example with Next.js:

  • pages/index.js => / (root page)
  • pages/posts/index.js OR pages/posts.js => /posts

Example with Remix:

  • routes/index.js => / (root page)
  • routes/posts/index.js OR routes/posts.js => /posts

Nested routes

Remix is built on top of React Router and by the same team so you can imagine who is the winner when it comes to nesting routes.
The idea behind it is to make an active route to mount a layout containing multiple nested routes, so each route manages its own content.

If I had to explain it in React words I would tell you to imagine every nested route were components and depending on the URL path they can be mounted and unmounted.

To achieve this Remix uses the React-router component. It is used on parent routes to tell them to render child route elements whenever they are triggered.
Remember all these nested routes are preloaded on the server so almost every loading state can be removed. Doesn’t it sound like a mix between SSR and SPA?

Next.js on the other side does support nested routes but they work as a separate page. If you wanted to achieve something similar to what Remix does you probably should customize your _app.js.

Example with both:

  • routes/posts/news/index.js => /posts/news

Dynamic routes

They work by rendering different content based on a URL param that is dynamic, but it uses a single route file.
In this case, both frameworks do support this functionality but use different naming conventions.

Example with Next.js

  • pages/posts/[postId].js => /posts/some-post-slug
  • pages/products/Agile web and app development/[productId].js => /products/keyboards/some-keyboard-slug OR /products/headphones/some-headphone-slug

It has its own hook, useRouter that will provide you the current value for those dynamic params (postId, category, productId).

Example with Remix

  • /app/routes/posts/$postId.js => /posts/some-post-slug
  • /app/routes/products/$category/$productId.js => /products/keyboards/some-keyboard-slug OR /products/headphones/some-headphone-slug

You could use useParam React Router hook to access those dynamic parameters.

What about routes that need to load files?

Imagine you need your website to contain the typical robots.txt and sitemap.xml files.

Both in Next.js and Remix you could add them to /public directory. But Remix lets you achieve it through the routing system, simply by creating a /app/routes/[sitemap.xml].js or /app/routes/[robots.txt].js

Data Loading

Both frameworks are Server-Sided so each one has different systems to fetch data and hand API calls.

In the case of Next.js you can choose between two options, depending on what type of page you want to build.

  • getStaticProps (SSG, ISR if revalidate interval is set) – it fetches data at build time and provides its data as the page’s component props.
export async function getStaticProps( {params} ) {
  const productList = await getProductList()

  return {
    props: {
        productList,
        slug: params.slug 
    }
  }
}
  • getServerSideProps (SSR) – it fetched data on the server-side at runtime and provides the returned data as the page’s component props.
export async function getStaticProps( {params} ) {
  const productList = await getProductList()

  return {
    props: {
        productList,
        slug: params.slug 
    }
  }
}
/**
 * Here you would have your own getStaticProps/getServerSideProps functions
 * depending on what system you choose.
 **/

  const PageName = (props)=> {
      const { productList } = props
      // Here you could render the requested data
      return (
 {JSON.stringify(productList, null, 4)} 

)
}

Remix uses a different way to load data

  • It provides you with a loader function to use on each route file which will work on the server-side at runtime.
  • On the page component itself, you will then be able to use useLoaderData hook to gather this data.
/**
 * Here you would have your own getStaticProps/getServerSideProps functions
 * depending on what system you choose.
 **/
export const async loader = ( {params} ) => {
    const productList = await getProductList()
    return {
        productList,
        slug: params.slug 
    }
};

export default function PageName() {
    const { productList } = useLoaderData();

    // Here you could render the requested data
    return (
 {JSON.stringify(productList, null, 4)} 

)
}

Data Mutations

Next.js doesn’t have a built-in way to perform mutations and you have to make it manually.
Remix on the other side has created a Form Component and uses it as you would do with a browser’s native HTML Form element. If you don’t enter any action URL it will use the same as the route for the form.

If the method is a GET, the loader the exported function will be triggered, while if the method is a POST, the action exported function defined in the component will be triggered.

Also, you can use the provided useTransition hook to manage the behavior of the application depending on the request status without having to manage it manually as usual.

export const loader = async ({ params }) => {
  // Each time this page receive a GET Request, this loader will be executed
  const userData = await getSomeUserData(params.slug) 

  return {
    userData // it contains the User Name
  }
}

export const action = async ({ request }) => {
  // Each time this page receive a POST Request, this loader will be executed
  const form = await request.formData()
  const content = form.get('content')

  return redirect('/')
}

export default function App() {
  const { name } = useLoaderData();

  return (
    
{`Hello there ${name}`}