Outshift Logo

INSIGHTS

16 min read

Blog thumbnail
Published on 03/14/2024
Last updated on 03/14/2024

Qwik vs. Next.js: Which framework is right for your next web project?

Share

Qwik is my go-to framework for web development projects over Next.js. In this article, I’ll explore the differences, pros, and cons of Qwik and Next.js. However, I believe Qwik, created by Builder.io, has the potential to be the future of web development.

Why Qwik is my go-to framework

I ultimately chose Qwik over Next.js for a variety of reasons, including developer experience, signals, level of control, the ability to use the broader React ecosystem, and the forward-looking features of the Qwik framework., Next.js is a phenomenal framework and I would not hesitate to recommend it. However, Qwik provides such a compelling developer experience and novel design that I get excited every time I get to code with it!

Background: From jQuery to the Qwik framework

I've been in software engineering as a fullstack engineer for close to 20 years.  My frontend journey started about 15 years ago.  I began with plain JavaScript and jQuery, then moved to KnockoutJS, AngularJS, and GWT. When React came on the scene in 2013, I was a very early adopter and fell in love. React has been my go-to library for close to 10 years now. I've also used a variety of other frameworks and libraries along the way but React has been my de facto frontend library until I discovered Qwik this year. 

What is Qwik?

Let's look at how the Qwik docs define itself: "Qwik is a new kind of framework that is resumable (no eager JS execution and no hydration), built for the edge and familiar to React developers." What does that mean though? Let's break it down.  

Qwik leverages JSX, so it feels like React, but one of the defining features is its resumability. "Resumability is about pausing execution on the server and resuming execution on the client without having to replay and download all of the application logic." In other words, render, pause, resume, render, pause, resume, etc.  

For the most part, this is all transparent to the developer without needing to add complexity. This is a fundamental difference between Qwik and other frameworks.  For example, In React, the page is rendered on the server, then hydrated on the client, then the page is interactive once all necessary JavaScript is downloaded. The exception here would be if dynamic imports were used, but this is still different than resumeability.

Qwik is designed so that the client/server boundary is mostly a non-issue. By default, everything renders on the server unless you specifically use a function, like useVisibleTask$ combined with isBrowser, to enforce rendering on the client only. Otherwise, all server rendering universally works with few exceptions.

This is only the tip of the iceberg though. I encourage you to read through the Concepts page of the Qwik docs, linked below, as Qwik is a truly unique framework for solving problems that other frameworks continue to have to mitigate.  

Qwik is quite new; it's only a few years old. It’s had little exposure by developers so far. I only recently discovered it at the All Things Open Conference. If this is your first exposure to the Qwik framework, please take the time to read through the docs. It's worth it. 

What is Next.js?

There is much written about Next.js so I'll keep this short and sweet. Next.js is the prominent framework that wraps the React Library. It's the current go-to framework for React. To quote from the docs, "Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations. Under the hood, Next.js also abstracts and automatically configures tooling needed for React, like bundling, compiling, and more. This allows you to focus on building your application instead of spending time with configuration."

Comparing Qwik vs Next.js

There are seven key areas I evaluated in my comparison of Qwik vs Next.js. For each, I name a winner so you can evaluate each feature based on what is most important to you.

Server vs. client

Next.js forces a very clear distinction between Server and Client Components, whereas Qwik, for the most part, completely makes this a non-issue. Everything is essentially server rendered by default, which I would consider a good thing overall.

Winner: The edge goes to Qwik 

Here is an example from the Next.js docs:

// Next.js code below

// SearchBar is a Client Component
import SearchBar from './searchbar'
// Logo is a Server Component
import Logo from './logo'
 
// Layout is a Server Component by default
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>
      <main>{children}</main>
    </>
  )
}

// ---
'use client'

export default function SearchBar({ children }: { children: React.ReactNode }) {
  return (
    <>
      <main>Search!</main>
    </>
  )
}

// ---
'use client'

export default function Logo({ children }: { children: React.ReactNode }) {
  return (
    <>
      <main>Logo!</main>
    </>
  )
}

In Qwik there is no need to define 'use client': 

// Qwik code below
import { component$ } from '@builder.io/qwik';

import SearchBar from './searchbar'
import Logo from './logo'
 
export default component$(() => {
  return (
    <>
      <nav>
        <Logo />
        <SearchBar />
      </nav>
      <slot />
    </>
  )
});

// ---
// SearchBar.tsx
export default component$(() => {
  return (
    <>
      <main>Search!</main>
    </>
  )
});

// ---
// Logo.tsx
import { component$ } from '@builder.io/qwik';
export default component$(() => {
  return (
    <>
      <main>Logo!</main>
    </>
  )
});

The code looks quite similar, and that's expected—it's JSX. The major point here is that in Qwik there’s no need to define 'use client' or 'use server' as everything is server rendered by default. This dramatically simplifies and improves the developer experience. While the above is a trivial example, if you've ever worked with Next.js you know that working between server and client components is a constant design choice and implementation consideration.  

Caching

Next.js gives significantly more control over caching. Qwik has caching and you can control the duration but cannot directly control invalidation. Whether that is a deal breaker or not has yet to be seen. In practice, it hasn’t been a significant issue, but I could foresee it becoming a pain point.  

Winner: Next.js 

Next.js lets you invalidate cache like so: 

// Next.js code below

export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // ...
}

'use server'
 
import { revalidateTag } from 'next/cache'
 
export default async function action() {
  revalidateTag('collection')
}

This is nice and a huge missing feature from Qwik. Qwik's approach is to re-run all routeLoader$s (fetch calls in the current page hierarchy) when a server action happens that may cause a mutation. It works, but fine grain control is missing. 

React ecosystem

Next.js naturally has native integration with the full React ecosystem. Qwik has access to the broader React ecosystem through the qwikify$ function, which the Qwik docs say should be considered as a migration strategy. This is because any React components wrapped in qwikify$ are rendered and hydrated isolated from one another, which can affect performance. The counterpoint here, however, is that Qwik also gives a lot of flexibility when this hydration happens. For example, you can tell Qwik to wait to hydrate the React component(s) until the browser becomes idle. There are many other control mechanisms besides idle.  

The other nice feature Qwik has is that it won't even pull down the React libs until the page is rendered containing the component. If you have a qwikified React component on page B, the React libs will never be loaded until both page B is hit in the Browser and various conditions are met, like if it's visible on the page (think of a modal that isn't yet visible). Qwik gives a lot more control than Next.js gives. While qwikify$ is considered a migration strategy, it works well, and you have various means to mitigate any potential performance issues.

Winner: Edge goes to Qwik 

// Next.js code below

'use client'
 
import { Carousel } from 'acme-carousel'
 
export default Carousel

// ---

import Carousel from './carousel'
 
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
 
      {/*  Works, since Carousel is a Client Component */}
      <Carousel />
    </div>
  )
}

You'll notice with Next.js you can't natively use a client component in a server component, so you still have to wrap the third-party component in another component that has 'use client.'

The story is similar with Qwik, however the level of control is greater. What I really like about Qwik's approach is the control over hydration. Next.js has no or little control here whereas Qwik allows you to control hydration on load, idle, hover, etc

// Qwik code below

/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import { Carousel } from 'acme-carousel'
 
export default qwikify$(Carousel, { eagerness: 'hover' })

// ---
// SomeComponent.tsx
import { component$ } from '@builder.io/qwik';
import Carousel from './carousel'
 
export default component$(() => {
  return (
    <div>
      <p>View pictures</p>
      <Carousel />
    </div>
  )
});

Charting

Qwik has no native charting library at the time of this writing. In React, you have access to numerous libraries, almost too many choices. That said, something like Chart.js would be trivial to integrate into Qwik, though it would still be client side rendered only. To leverage the full power of Qwik, a charting library needs to be created that can be server side rendered. Until then, integration is easy with any Charting library, but they'd all be client rendered only. The user experience is fine but not having the option to have native server side rendered is still missing. As a side note, you could potentially use a svg charting library or manual svg to render server side, but there isn't a formal Qwik charting library that I've seen do this yet.

Winner: Next.js due to native charting libraries in the React ecosystem

State management

Qwik natively has signals. And if you've used Signals compared to React useState, there is simply no comparison. Signals win hands down. There is an open issue to get Signals in Next.js, but the conclusion is this needs to be done in the React library itself. There are some users who have reported success in monkey patching Preact signals into Next.js but the results seem mixed.

Winner: Qwik  

// Next.js code below

'use client'

function HomePage() {
  // ...
  const [likes, setLikes] = React.useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>Likes ({likes})</button>
    </div>
  );
}
// Qwik code below

export default component$(() => {
  // ...
  const likes = useSignal(0);
 
  return (
    <div>
      {/* ... */}
      <button onClick={() => likes += 1}>Likes ({likes})</button>
    </div>
  );
})

You can also pass Signals as props to child components and mutate them there as well This is not directly possible in React without callback functions. 

// Qwik code below

// Parent.tsx
export default component$(() => {
  // ...
  const likes = useSignal(0);
 
  return (
    <div>
      <Child likes={likes} />
    </div>
  );
})

// Child.tsx
type Props = {
  likes: Signal<number>;
};
export default component$<Props>((props) => {
  return (
    <div>
      <button onClick={() => props.likes += 1}>Likes ({props.likes})</button>
    </div>
  );
})

Dev server

Qwik uses Vite and Vite is becoming one of those main stays as a dev server frontend works. Vite has some incredible features, such as a built-in reverse-proxy and very efficient handling of modules and hot module reloading. See Why Vite for more information. Next.js is still very fast to build with SWC and dev with Turbo but Vite has the edge here.

Winner: Edge to Qwik  

Server-side rendering

While I covered this in the Server vs. Client section, I want to dive deeper into the Server-Side Rendering here.

When thinking about rendering server components and when the browser receives its first HTML from the framework, the story gets complicated very quickly. Next.js and Qwik accomplish the same task, albeit in a different manner. The result is effectively the same at face value, but there are framework specific control mechanisms that provide a different developer experience. If you read Next.js's loading-ui-and-streaming doc, you can leverage React Suspense to have ‘instant’ loading then progressive resolution of the UI. This is very nice and there is no immediate analog to this in Qwik, but you can still accomplish the same thing.

According to Next.js, “Navigation is immediate, even with server-centric routing.” Let me describe the core issue here a bit more in depth.  With server-side rendering the component first loads a list of products, for example, from some external source (most likely).  Next, the framework renders the component and produces HTML, and you won't see the rendered page until the backend fully loads the products and renders the HTML. Therefore, with no cache and a slow external API, let’s say five seconds, the user won’t see any HTML for the products page rendered for a full five seconds. We can agree that this is a bad user experience. The browser appears to be doing nothing or to be unresponsive.  

How Next.js handles this is by telling you to use a loading.js to leverage React Suspense. Suspense allows you to render a fallback component while the data is loading. Then, when the data is loaded, the fallback component is replaced with the actual component. This is a really nice feature and makes for a great developer experience.

Qwik handles this differently. Qwik has a function called a routeLoader$, which is run exclusively on the server. The Promise must be resolved before the page is rendered. So, in the case of the products component, the routeLoader$ would be called, the Promise would be resolved after five seconds, then the page would be rendered. There is no concept such as Suspense in Qwik, but you can still accomplish the same thing with server$ streaming. The difference here is that you must manage the data loading yourself, however you have more control over the data loading. For example, you could load the first 10 products, then render the page, then load the rest of the products. This is a contrived example, but it illustrates the point. There is an interesting GitHub issue for Qwik demonstrating an example of loading data with streaming. You’ll notice the significant complexity of doing this in Qwik. This is where Next.js wins with its simplicity.

Winner: Next.js due to the developer experience with React Suspense. Qwik, however, potentially has more fine-grained control and can accomplish the same thing, just not as seamlessly.  

Why I chose Qwik

  • Qwik is ultimately easier to develop in as it has a better developer experience— you don’t have to manage the server vs. client components for the most part. It’s not even that Qwik goes out of its way to abstract that, it’s a fundamental design of Qwik where everything is rendered server side initially unless you specifically go out of your way to make it client side rendered. No marking ‘use client’ or ‘use server’, it just works, and you don’t think about it.
  • While the Qwik ecosystem is in its early stages, you still have access to the broader React Ecosystem. Yes, there is a penalty for hydration, which in practice is typically negligible, but that hydration penalty exists regardless with no alternative in Next.js. The silver lining in Qwik is that you both have control over when hydration happens, and you can eventually rewrite/refactor the React component to be Qwik native.
  • Signals are superior to React useState, I don’t think there will be much disagreement on this point. If anything, some might argue for RxJS over Signals, but that’s a different discussion.
  • I believe the Qwik way of resumability represents a possible cornerstone of frameworks going forward. Even React Server Components do something similar with serializing data to the client after rendering. However, with RSCs, “All code written for Server Components must be serializable, which means you can’t use lifecycle hooks, such as useEffect() or state,” whereas Qwik doesn’t have this limitation. I believe Qwik’s approach is currently superior, though RSCs are a step in the right direction. That doesn’t mean that Qwik itself will necessarily be the defacto framework in the future, but it’s future and forward looking, and its approach solves many problems that other frameworks (like Next.js) must mitigate.
  • By default, in Next.js (or any React framework) the more third-party components you add the larger the bundle size will be to the browser. There is a linear relationship here. In Qwik, however, there is a lot more control and not a direct linear relationship. No JavaScript is delivered to the browser by default unless specifically needed. You can have a component that contains a charting library, for example, and even if the library is imported on the page, you can control when that library is loaded. That means if you have a charting library that is only used on a modal, you can tell Qwik to only load that library when the modal is opened. This is a huge win for Qwik. In Next.js you can do this with dynamic imports, but it’s not as straight forward as Qwik. Qwik also has a lot more control features than the scenario I just mentioned.
  • Qwik allows streaming server responses from an async generator in an onClick from the client. This is some magic sauce right here if you look at this example. It’s not impossible something could be hacked together to mimic this in Next.js/React with React Server Components, but it wouldn’t be exactly how Qwik is doing this as it is a fundamental design of Qwik that supports this.
  • useTask is like React useEffect except since Qwik uses Signals, the usage is significantly straighter forward than useEffect + useState in React. It’s much less boilerplate code and the logic makes more sense. 

Conclusion: The Qwik framework takes the win

You can’t go wrong with either Next.js or Qwik. Both have great documentation, both have momentum, and both are used in production. While I presented many technical areas that I believe Qwik excels at, what I really get excited about is the intangible feel of developing in the framework. Not every framework or language has that intangible feel to it. Qwik has it, and it feels great every time I get to code with it.

Sign up for the Shift newsletter for more of the latest insights on emerging technology. 

Subscribe card background
Subscribe
Subscribe to
the Shift!

Get emerging insights on emerging technology straight to your inbox.

Unlocking Multi-Cloud Security: Panoptica's Graph-Based Approach

Discover why security teams rely on Panoptica's graph-based technology to navigate and prioritize risks across multi-cloud landscapes, enhancing accuracy and resilience in safeguarding diverse ecosystems.

thumbnail
I
Subscribe
Subscribe
 to
the Shift
!
Get
emerging insights
on emerging technology straight to your inbox.

The Shift keeps you at the forefront of cloud native modern applications, application security, generative AI, quantum computing, and other groundbreaking innovations that are shaping the future of technology.

Outshift Background