Published on 00/00/0000
Last updated on 00/00/0000
Published on 00/00/0000
Last updated on 00/00/0000
Share
Share
INSIGHTS
16 min read
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.
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!
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.
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.
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."
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.
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.
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.
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>
)
});
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
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>
);
})
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
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.
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.
Get emerging insights on emerging technology straight to your inbox.
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.
The Shift is Outshift’s exclusive newsletter.
Get the latest news and updates on generative AI, quantum computing, and other groundbreaking innovations shaping the future of technology.