Alex Merced I am a developer, educator, and founder of devNursery.com.

Remix vs. Next.js vs. SvelteKit

6 min read 1844

Next vs Remix vs Sveltekit

Remix, a full-stack web framework for from the creators of React Router, has gone from a paid to a free model, which is big news within both the React and the meta-framework communities.

Within the past few years, the usage of the SaaS paradigm, which is the business model typically used by open source technologies in the cloud, has been solidified within the industry. For example, React meta-frameworks Next.js and Gatsby both offer paid hosting services with additional features tailored to optimize their respective framework.

Shopify recently released a React meta-framework called Hydrogen and has plans to release a hosting service for it called Oxygen. Similarly, databases like Neo4j, ArangoDB, and MongoDB each offer cloud database services, making adoption and usage easier. Eventually, the Remix creators will release an individualized, optimized platform.

Meanwhile, Vercel, the creators of the Remix competitor Next.js, has had an interesting development in hiring Svelte creator Rich Harris to work full time on SvelteKit, the primary Svelte meta-framework.

As a framework for server-side rendering, Remix aims to fulfill some of the same needs as frameworks like Next.js and SvelteKit. In this post, we’ll compare a few of their features, ranging from initiating a project to adding styling. Let’s get started!

N.B., the equivalent meta-frameworks for Vue and Angular would be Nuxt.js and Angular Universal, respectively. The new upstart framework Solid doesn’t have a meta-framework yet, but it is only a matter of time.

Initiating a project

First, we’ll consider the commands for creating a project in each framework.

//Remix
npx [email protected]

When generating a Remix project, you can select a template tailored for deploying to different host services, like Netlify, Cloudflare, Fly.io, Vercel, and more. Each comes with the documentation to make deployment a breeze.

//Next.js
npx [email protected]
//SvelteKit
npm init [email protected] my-app

Routing

Routing determines what URL will be needed to access different pages on the website. All three frameworks use file-based routing, which is primarily what all meta-frameworks use. The URL is based on the name and location of the file for that particular page.

Below, you’ll see some examples of how different files get mapped to URLs, including an example with URL params, which define a part of the URL as a variable that you can retrieve.

Remix

Remix is built on top of React Router v6, so you can use many of the newest Hooks like useParams and useNavigate in your client-side code to handle navigation, similar to using React Router v6 normally.

We made a custom demo for .
No really. Click here to check it out.

  • /app/routes/index.js
  • /helloapp/routes/hello/index.js or app/routes/hello.js
  • /use_param/:idapp/routes/use_param/$id.js

Next.js

  • /pages/index.js
  • /hellopages/hello/index.js or pages/hello.js
  • /use_param/:idpages/use_param/[id].js

SvelteKit

  • /src/routes/index.svelte
  • /hellosrc/routes/hello/index.svelte or src/routes/hello.svelte
  • /use_param/:idsrc/routes/use_param/[id].svelte

Loading data on the server-side

A major benefit of using a meta-framework is handling a lot of data preparation prior to your page hydrating, like API calls, transformation, etc. When you use a meta-framework, you don’t have to prepare loaders or things like the useEffect Hook to deal with the asynchronous nature of these issues.

In all three frameworks, there is a function on each page we can define that will be run from the server prior to shipping the page to the user’s browser.

Remix

In Remix, we define a function called loader, which will be passed an object with things like URL params and the request data that we’lll use to prepare any data. The loader function can then return any data that the page will need, which can be retrieved in the component using the useLoaderData Hook as follows:

import { useLoaderData } from "remix";

export let loader = async ({ params, request }) => {
   // get a param from the url
   const id = params.id
   // getting data from the url query string
   const url = new URL(request.url)
   const limit = url.searchParams.get("limit")

   return {id, limit}
};

export default function SomePage() {
  let {id, limit} = useLoaderData();
  return (
    <div>
      <h1>The params is: {id}</h1>
      <h1>The limit url query is: {limit}</h1>
    </div>
  );
}

Next.js

Similarly, in Next.js, you can export a function called getServerSideProps. The return value can then define the props to the page component:

export const getServerSideProps = async ({ params, query }) => {
   // get a param from the url
   const id = params.id
   // getting data from the url query string
   const limit = query.limit

   return {props: {id, limit}}
};

export default function SomePage() {
  let {id, limit} = useLoaderData();
  return (
    <div>
      <h1>The params is: {id}</h1>
      <h1>The limit url query is: {limit}</h1>
    </div>
  );
}

SvelteKit

With SvelteKit, you define a function called load in a separately designated script block. Just like the previous examples, you can handle any API calls and data preparation, then return the data to be used as props to the page component:

<script context="module">
        // Load function to define data
        export function load({ page }) {
   // get params from url
   const id = page.params.id
   // get data from url query
   const limit = page.query.get("limit")
                return {
                        props: {
                                id,
            limit
                        }
                };
        }
</script>

<script>
   // normal client side javascript block
        export let id;
   export let limit
</script>

<div>
   <h1>The params is: {id}</h1>
   <h1>The limit url query is: {limit}</h1>
</div>

Pre-rendering pages as static site generators

Pre-rendering pages as static site generators is probably the biggest diversion in feature sets. At the time of writing, Remix does not support pre-rendering of pages, while Next.js and SvelteKit do, meaning you can also use them as static site generators.

Remix

Remix does not currently support static site generation, but it provides a guide on using distributed cloud technologies to optimize your app.

Next.js

If you prefer that your page is pre-rendered, simply export getStaticProps instead of getServerSideProps. Otherwise, we’ll observe the same pattern as before.

SvelteKit

If you want a page to be pre-rendered in the module script blog, just set the following code:

export const prerender = true;

The code above will tell SvelteKit to pre-render the page instead of rendering it on each request.

API Routing

While we can handle logic on the server side with the loader, getServerSideProps, or the load function, API keys and other data shouldn’t be in this code. You may still need dedicated API URLs with code that is only visible and run on the server side.

Remix

If you create a file that doesn’t export a component, then it will be treated as a resources route that can make a JSON response:

export function loader({ params }) {
  const id = params.id
  return new Response({id}, {
    status: 200,
    headers: {
      "Content-Type": "application/json"
    }
  });
}

Next.js

If you create a route that exports a route function like in Express.js within the pages/api folder, it will be treated similarly to an API route:

export default function handler(req, res) {
  res.status(200).json({ id: req.params.id })
}

SvelteKit

If you have a JavaScript or TypeScript file instead of a Svelte file, export a function, and it will be treated as an API route. The name of the function will determine what method it is a response to:

export async function get({ params }) {
        return {
          body: {id: params.id}
        }
}

Styling

When it comes to styling, the three frameworks can differ quite a lot.

Remix

Remix has a built-in way of linking traditional CSS style sheets via link tags by exporting a link function in the pages JavaScript file. If a link tag is present in the root (the template), the page’s link tag will be inserted afterward. Therefore, you don’t need to have all of your CSS present on every page to optimize how much CSS you’re sending per page:

export function links() {
  return [
    {
      rel: "stylesheet",
      href: "https://unpkg.com/[email protected]/dist/reset.min.css"
    }
  ];
}

Next.js

You can use the helmet component to add link tags as well, but you can also use styled-components, JSS, Emotion, Sass, or any other CSS abstraction along with just importing standard CSS style sheets.

SvelteKit

Svelte, like Vue, uses single-file components, so the CSS is in the components file.

Where does Remix really differ?

Remix truly has a unique and valuable difference in the way it handles forms. In modern frameworks, we’ve abandoned the traditional functionality of forms in place of hijacking the process from within JavaScript. For those who’ve developed web applications long ago, you may remember forms that look like this:

<form method="post" action="/user/new">
        <input type="text" name="username">
        <input type="password" name="password">
        <input type="submit" value="new user">
</form>

Both the request method and the place where we made the request were entirely defined in the form, so there’s no need for onSubmit handlers or preventDefault. In our case, it worked because we had Perl or PHP script waiting on the other end to handle that request. Remix has a custom Form component that attempts to embrace the feel of this type of experience.

Here is a sample component from the Remix documentation illustrating the Form component in action:

import type { ActionFunction } from "remix";
import { redirect } from "remix";

// Note the "action" export name, this will handle our form POST
export const action = async ({
  request
}) => {
  const formData = await request.formData();
  const project = await createProject(formData);
  return redirect(`/projects/${project.id}`);
};

export default function NewProject() {
  return (
    <form method="post" action="/projects/new">
      <p>
        <label>
          Name: <input name="name" type="text" />
        </label>
      </p>
      <p>
        <label>
          Description:
          <br />
          <textarea name="description" />
        </label>
      </p>
      <p>
        <button type="submit">Create</button>
      </p>
    </form>
  );
}

When the form is submitted, it will make a POST request to /projects/new. The form will use that exported action function for handling, then redirect to the proper route. What’s old is new again!

Bottom line

If you want to bring some of that old-school, full-stack web application feel but still capture the benefits of React on the client side when needed, Remix is a strong choice for your meta-framework.

Although the lack of static pre-rendering makes it difficult to use for certain use cases, like blogs and marketing funnels, Remix is a strong addition to the toolkit for sites with lots of dynamic content.

One thing is for sure: the place to look for innovations right now in the frontend framework space is really in the meta-frameworks, like Remix, Next.js, SvelteKit, Nuxt, Gatsby, Gridsome, etc. We are even entering an era where meta-meta-frameworks exist — I’m looking at you, Blitz.js (built on Next.js).

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Alex Merced I am a developer, educator, and founder of devNursery.com.

Leave a Reply