Home / blogs

Until recently, the frontend framework running under the hood of TechnoBlick was Preact. We paired it with Next.js — our chosen framework for core app features like server-side rendering (SSR), routing, and data fetching. Preact's small size and compatibility with Next.js made it a logical choice at the time.
But things changed. Recent developments in the JavaScript frontend ecosystem, combined with long-term pain points in our setup, led us to migrate to React. Here's the full story of how — and why — we made the switch.
We ran Preact with a compatibility layer and the Next.js Preact plugin to make it work seamlessly with Next.js and common React features. Our package manager aliased React libraries to Preact under the hood. We also hosted on Vercel, which natively supports many Next.js features — and since we relied heavily on those features, staying current with the latest framework versions and development experience (DX) was a priority.
The JavaScript and React ecosystem evolves constantly, and keeping up is essential for project health and adopting new capabilities. Over time, we noticed our Preact setup required more and more workarounds to stay compatible with Next.js. While production ran fine, our development mode suffered from:
Unreliable hot reloading
Poor error handling
An overall slow environment and rendering performance
The slow rendering was the most painful issue. Development mode with Preact was noticeably slower than expected, causing constant browser tab freezes and sluggish hot reloads after nearly every code change — regardless of whether we were editing a simple component or a complex page. This bottleneck affected the entire team's productivity.
Our first fix attempt — disabling the Preact/Next.js compatibility plugin — improved performance but broke hot reloading entirely. Re-enabling the plugin brought back the original slowdowns; the setup simply had too many moving parts, custom configurations, and overrides to stabilize.
We also profiled rendering performance, looking at state changes during app initialization and re-render frequency. While this surfaced a few optimization opportunities, it didn't resolve the core issue. Eventually, we tried running a basic Next.js configuration without Preact — stripping out all overrides, plugins, and custom config as a quick "make it work" test. It worked.
With migration clearly necessary, we built a proof-of-concept (POC) running on React to validate our assumptions. Several team members even used a patched version of the app while building new features, and the feedback was unanimous: this version solved every issue we'd been fighting with Preact. 💪
Knowing React would fix our development problems was one thing — migrating an entire frontend framework is still a significant undertaking. Our main goal was to restore a healthy development environment without sacrificing delivery speed.
Because the TechnoBlick monorepo supports multiple apps (a browser extension, a web app, and a mobile PWA), we needed to ensure every app continued working and that we could keep shipping existing features without delays or downtime. We also had to avoid introducing new bugs, regressions, or performance/bundle-size issues, and keep the codebase clean and maintainable.
With those constraints in mind, we weighed the pros and cons of moving to React.
100% compatibility with Next.js
Access to advanced Next.js features going forward, and the ability to keep pace with major React versions, new hooks, and APIs
Less setup code to maintain — just the default Next.js config, with no bundle aliases, overrides, or custom configuration
Fewer third-party libraries required
More consistent APIs — Preact was simultaneously supporting React compatibility while introducing its own new features (like signals), creating multiple, less predictable paths of development
Backed by major companies including Vercel and Meta (Facebook)
Increased bundle size (Preact's footprint is significantly smaller than React's)
Extensive project changes, since many parts of the codebase referenced Preact and its libraries
Technical debt risk — Preact permits certain patterns, like invalid hook calls or context usage, that are silently allowed via disabled lint rules but would break the app outright in React
Time allocation and scheduling challenges around other development priorities during the migration
One of the original reasons we chose Preact was that it delivers the same core functionality as React in a much smaller footprint — so bundle size was a critical factor to evaluate before migrating.
We tested production bundle size across page sizes and chunk types, using Next.js's build output to compare before-and-after results directly.
The result: a consistent 34kb increase in "First Load JS" across all pages — the exact size difference between the React and Preact framework bundles. Since this shared framework bundle loads once and is cached across all pages, every page saw the same increase.
Importantly, "First Load JS" only applies the first time a user lands on a given page; after that, subsequent page sizes matched the "Size" column, which stayed within roughly 1kb of the original Preact numbers. This confirmed React didn't introduce any additional overhead beyond the framework bundle itself — and because that bundle size is predictable and browser-cacheable, the real-world impact is partially mitigated.
This analysis also surfaced a few bundle optimization opportunities. While we didn't implement them as part of the migration, we logged them for future maintenance work.
With the analysis complete, the team committed to the migration — and decided to tackle it as a hackathon project during an upcoming in-person team gathering in Poland. As a fully remote team, gatherings like this are rare opportunities to collaborate face-to-face with tighter, more synchronous communication than usual.
The timing was ideal — no major feature deployments were scheduled, so the whole team could focus entirely on the migration. With everyone in the room for roughly four working days (most people flew home Friday), thorough upfront planning was essential to make the most of a short window. We identified three core focus areas for the migration.
This meant removing all Preact overrides and dependencies first, so every team member could start from a clean, consistent codebase. The same cleanup was required for our browser extension, which used Preact but had a separate build process from the Next.js app.
With an extensive test suite, we needed every test passing green with no new bugs or regressions — including swapping in the React-compatible version of Jest.
Because we were already using standard React APIs through the Preact compatibility layer, most components transferred cleanly. However, Preact is more permissive than React in certain areas, which surfaced some existing bugs — and gave us a chance to improve the production app in the process.
We categorized issues into warnings and errors:
Warnings were mostly invalid props passed to underlying DOM elements — harmless to functionality, but cluttering the console and complicating debugging.
Errors stemmed from invalid React API usage that caused real bugs, such as setting state during rendering, accessing context outside React components/hooks, or event-handler memory leaks.
The planning phase produced:
A decision record (DR) documenting the full analysis and migration rationale
An epic branch and pull request on GitHub containing all core project changes
A clear, actionable task list for execution
When the gathering arrived, the whole team dove in. ⌨️
By the end of day one, we'd completed all core project changes and resolved most of the warnings across the codebase — clear proof that the planning phase had paid off.
Days two and three went smoothly, with most remaining issues tied to flaky tests surfaced by the React version of Jest. We also fixed several memory leaks and re-render issues simply by correcting how we used certain React APIs.
On the final day, the team focused on code review and end-to-end (E2E) testing to validate all core user flows, plus a final bundle size check to confirm nothing regressed.
Every team member reviewed the full epic branch before wrapping up. We didn't quite merge to production that Friday — but we still celebrated the milestone with the team. 🍻 The migration merged to production cleanly the following week, with no issues.
This migration started because months of poor developer experience were slowing the team down and blocking new feature delivery. 😥 We tried several fixes, but what ultimately worked was structure, planning, and strong teamwork. ✅
The migration required significant effort, but it succeeded — and it sets up a healthier foundation for all of our apps going forward. 🚀
Our previous Preact compatibility layer was blocking upgrades to the latest versions of core libraries like Next.js. Moving to React clears that path, letting us adopt new features that will keep improving the product. 🙌 While we didn't tackle major library upgrades during the migration itself, we're excited for what's ahead.
Our monorepo is open source, so if you'd like to explore the full migration — including the main pull request and related changes — you're welcome to dig through the history yourself. Enjoy! 🎉
Related tags:

Repositories store project files and their full revision history. The key difference between the two types comes down to who can see and interact with that code.

Full-stack development is evolving rapidly, making it essential for developers and businesses to choose technologies that are scalable, secure, and future-ready. In this guide, we'll explore the best front-end, back-end, database, and DevOps technologies for building modern web applications in 2026.

Server-side rendering (SSR) is one of the core ideas behind why Next.js has become so popular for building modern web applications. Instead of sending a mostly-empty HTML page to the browser and letting JavaScript build the interface afterward, SSR generates the fully-rendered HTML on the server first, then sends that complete page to the user.