How I migrated from Next.js to Qwik

My two-week journey of migrating from Next.js to Qwik and Qwik City

First things first: how it all ended

If you want to skip the whole story and just see the result, here you go:

  • has been rewritten from scratch (thus shall be called v2 from here now on) and now uses Qwik and Qwik City as its underlying framework, previously it used Next.js
  • I removed all depdencies related to the 3rd party CMS and Caching Layer and self host all content in the Github repo of this PWA (Progressive Web App)
  • I did this as I noticed that maintaining the whole stack of v1 of was too much work - the final straw was when I had to update the CMS (Sanity) and would have to rewrite some stuff anyway
  • The v1 translation component, which involved a custom parser of documents and the translation via Google Translate, was replaced by pure markdown content and OpenAI's GPT-4, respectively
  • I haven't yet implemented the TTS (Text-to-Speech) feature, but I will do so in the near future by using OpenAI's Whisper model
  • is still hosted on Vercel, but now uses Edge Functions to serve the site, instead of the Build Output API, as there's currently no deployment-adapter for it provided by Qwik City
  • Design got updated as well and now support both light and dark mode by using Windy Radix Colors to have Radix Colors available as Tailwind classes version 2 start page

The CMS is gone, long love the CMS

The primary reason I wrote v2 of was to cut down on service dependencies:

  • remove CMS (and it’s CDN) and manage as well as host all content (posts, categories, assets, etc) directly in the repository, which will also be open-sourced
  • as part of this transition, remove caching-layer for content of CMS, as it’s not needed any more
  • replace usage of Google Translate as well as Microsoft text-to-speech with APIs from OpenAI

Transitioning away from a powerful CMS like Sanity and it’s dynamic query builder comes at the cost of having to implement some (lightweight) content management myself. I don’t want to care about having to update and maintain such a core piece of the project. My goal is to have this solution working even if I don’t write a post for a year, because the only requirement should be to open the repo, write an article and then simply push changes on main to trigger a new deployment.

You can inspect the /cms-directory at the root of the project at GitHub to see the source code for yourself. It works quite well, is straightforward and completely standalone, no extra account and login for a CMS required. The exiting experience is now actually more powerful than before, as I can now use Markdown for all content, which is much easier to write and maintain than the custom editor of Sanity. I added a live-reload option for running the editor-script, so that I can see the changes in real-time - if I want even for multiple languages at once.

I will go into more detail about the CMS in a separate post.

Why I chose Qwik and Qwik City instead of Next.js

Qwik and its Full-Stack Framework called „Qwik City“ sounded very intriguing to me from the beginning: lazy loading any JS automatically until it’s actually needed without any hydration. In the context of, the gains from this architecture are diminishing, as my site is at its core a blog. The more components and interactions a web app has, the bigger the improvements are that come from using Qwik. Still, I was curious and wanted to get some first-hand experience with it.

i18n in Qwik compared to Next.js is translated into 14 languages, including some right-to-left ones. When I first took a look at Qwik at the start of 2023 (around 10 months before I did the actual v2 rewrite), I felt that there was no real solution for i18n, and my efforts beyond some initial tests faded.

When looking at it again in December, qwik-speak was the solution required to enable translations in, so I dusted off the old codebase and kicked-off the rewrite.

A new translator

The old stack used the Google Translate API, which served me well, but the whole component came with some complexity:

  • content in the original language was fetched from Sanity CMS, parsed and split into intermediate chunks
  • Each chunk got then translated into the available target languages
  • The translated chunks got rebuild so that the original content, incl. formatting, was restored and available for the Frontend to be

It worked well enough, but the code was relatively hard to read and maintain. now uses OpenAI to translate all content. The whole setup is much simpler:

  • new article gets written in plain Markdown
  • the article gets sent to OpenAI and translated as a whole
  • LLMs are intelligent enough that they only translate the relevant parts, but not e.g. the whole frontmatter (a prompt gives guidance to the LLM in regards to which fields shouldn’t be subject to the procedure)

Deployment on Vercel

Qwik City could be exported as a static site (SSG), but this would mean that I couldn’t use dynamic endpoints, therefore is using the „Vercel Edge“-adapter for continued deployment and hosting via Vercel. I would like to have the „Build Output API“ from Vercel also be available as an adapter, but for now it’s sufficient. previously used the Build Output API, which meant that server-side code used Node.js-APIs - on Vercel Edge, this is not possible any more, as it’s a somewhat weird and confusing mix of Web and Node.js APIs. This required me to rewrite some components for compatibility.

AI and transparency

While not ready for the initial release of v2, I will update the metadata properties for new articles to have more information about which aspects of an article were created using AI.

In the same move, I‘ll also make the analytics of the site publicly available to further boost transparency. uses, a privacy-respecting analytics-service for basic usage aggregation.

A new design version 2 article hero

As part of the whole rewrite I also updated the design a little bit. It’s similar on most pages, but with a fresh coat of paint. version 2 article metadata fields

New colors + light and dark mode

Thanks to Windy Radix Colors, suppport for light and dark mode was easy to implement as the library provides all color variants for both modes. version 2 all posts page in dark mode version 2 all posts page in light mode

I‘m now using the Radix Color palette as a Tailwind-plugin, which comes with a careful selection of color variants as well as easy support for a light/dark mode. I also switched to Lucide Icons as they're available via a library for Qwik. For comparision, here are some screenshots of the old design: version 1 start page version 1 article hero

Conclusion and what's next

The v1 of served me very well since its initial launch, but I felt the stack had to be simplified towards dependencies on other services.

It took me around two weeks to finish the rewrite. Management if content will be much simpler from now on and also help me avoid excess costs.

Version 2 of hasn't reached feature parity with v1 for its launch, but I'll add the missing features in the coming weeks, such as the like/dislike button or the TTS (Text-to-Speech) feature.