Frak Labs
devops 8 min read

The DevX Revolution: How We Cut Build Times by 10x at Frak

From Next.js to TanStack Start, Rollup to Rolldown, and 5-minute builds to 30 seconds

DevX Rolldown Vite TanStack Start Vitest Performance Monorepo

hero image

In software development, speed is a feature, not just for the end user, but for the developer. A slow CI pipeline or a sluggish hot module replacement (HMR) loop kills flow states and slows down innovation.

At Frak, we recently undertook a massive architectural migration to achieve state-of-the-art Developer Experience (DevX). We migrated our business dashboard from Next.js to TanStack Start, unified all routing on TanStack Router, adopted Rolldown for blazing-fast builds, moved our entire test suite to Vitest, and rebuilt our SDK pipeline with tsdown.

The result? Our business app build time dropped from 5 minutes to under 30 seconds. Our complete deployment pipeline, including backend, 3 frontends, and all SDKs, now completes in 4 minutes. And our 3,119 unit tests execute in 42 seconds.

Here’s how we overhauled our stack to make our tooling scream.

1. Business Dashboard: From Next.js to TanStack Start + Nitro

Our primary business dashboard was built on Next.js, deployed to AWS Lambda via SST and OpenNext. While this setup worked, it had significant pain points:

We migrated to TanStack Start with Nitro handling the SSR layer. This was a breath of fresh air:

The mental model is simpler, the builds are faster, and we have complete control over hosting.

2. Routing Unification: Everything on TanStack Router

We had a fragmented routing landscape: some apps used React Router v7, others used file-based routing. We unified everything on TanStack Router across:

Why TanStack Router?

TanStack Router gives us compile-time type safety for routes, params, and loaders. Instead of file-system conventions checked at runtime, we define routes explicitly:

// app/routes.ts
import { type RouteConfig, index, layout, route } from "@react-router/dev/routes";

export default [
    index("./views/landings/home.tsx"),
    
    // Layout-based routing for the main wallet interface
    layout("./views/layouts/wallet.tsx", [
        route("/login", "./views/auth/login.tsx"),
        route("/recovery", "./views/auth/recovery.tsx"),
        
        // Protected routes requiring authentication
        layout("./views/layouts/protected.tsx", [
            route("/wallet", "./views/protected/wallet.tsx"),
            route("/settings", "./views/protected/settings.tsx"),
        ]),
    ]),
] satisfies RouteConfig;

This unified approach means every developer on the team uses the same mental model, regardless of which app they’re working on.

3. The Rolldown Revolution: 70-80% Build Time Reduction

Vite is great, but as our codebase grew, the Rollup-based production build started to slow down. Enter Rolldown, a Rust-based bundler designed to be the future of Vite (and now integrated into Vite 6).

We didn’t wait for the stable release. We adopted rolldown-vite (the experimental fork) immediately and saw 70-80% reduction in build times across the board.

Why Rolldown Matters

Beyond speed, Rolldown gave us two critical improvements:

  1. Unified configuration: Since we now use Rolldown everywhere (apps, SDKs, packages), we can share configuration logic across the entire monorepo
  2. Intelligent code splitting: Much better chunking options compared to Rollup, essential for our wallet which loads heavy crypto dependencies (Viem, Wagmi, @noble/*)

The “Override” Hack

To enforce Rolldown across our entire monorepo without rewriting every package config, we used a package manager override:

// package.json
"overrides": {
    "vite": "npm:rolldown-vite@^7.1.20"
}

Advanced Chunking Strategy

Rolldown gave us granular control over chunk splitting. We configured it to isolate our heavy cryptographic dependencies from the UI code. This ensures that the user sees the interface immediately, while the blockchain logic loads in the background.

// vite.config.ts
export default defineConfig({
    build: {
        rolldownOptions: {
            output: {
                advancedChunks: {
                    minShareCount: 2,
                    groups: [
                        // Isolate React to cache it aggressively
                        {
                            name: "react-vendor",
                            test: /node_modules[\\/](react|react-dom)/,
                            priority: 40,
                        },
                        // Isolate heavy crypto libs (Viem, Wagmi)
                        {
                            name: "blockchain-vendor",
                            test: /node_modules[\\/](viem|wagmi|@noble)/,
                            priority: 35,
                        },
                    ],
                },
            },
        },
    },
});

This change alone reduced our cold start time by ~40%.

4. SDK Build Pipeline: From rslib to tsdown (Rolldown-powered)

Our SDK wasn’t left behind. We previously used rslib (Rspack-based) to build our SDK packages, but maintaining two different build systems (Vite for apps, Rspack for libraries) was mentally taxing.

We migrated to tsdown, a zero-config bundler powered by Rolldown:

Our SDK structure:

One shared configuration, consistent build behavior, and blazing-fast iteration.

5. Testing Unification: Everything on Vitest

The final missing piece was testing. We had fragmented test runners:

While Bun test was fast, it lacked the ecosystem and configurability of Vitest. We migrated everything to Vitest 4.0 with the Projects API.

The Numbers

Vitest Projects API

We use the Projects API to run all tests in a single command while keeping environment-specific configurations:

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
    test: {
        projects: [
            {
                name: 'wallet-unit',
                environment: 'jsdom',
                include: ['apps/wallet/**/*.test.ts'],
            },
            {
                name: 'backend-unit',
                environment: 'node',
                include: ['services/backend/test/**/*.test.ts'],
            },
            // 5 more projects...
        ],
        pool: 'forks',            // Parallel execution
        restoreMocks: true,
    },
});

Now we run bun run test once, and Vitest orchestrates everything: frontend tests with jsdom, backend tests with Node environment, all in parallel.

Benefits

6. Deployment Pipeline: 4 Minutes for Everything

With all these optimizations in place, our deployment pipeline is now:

  1. Build base Docker image with all SDK packages (~1 min)
  2. Push to private registry (pause while this completes)
  3. Build application images in parallel:
    • Backend (Elysia.js + Drizzle)
    • Wallet frontend
    • Listener frontend (iframe layer)
    • Business SSR app (TanStack Start + Nitro)
  4. Deploy to Kubernetes cluster

Total time: 4 minutes from commit to production.

This is a massive improvement from our previous setup:

The Results: By The Numbers

Here’s what we achieved with this migration:

MetricBeforeAfterImprovement
Business build time5 min30 sec10x faster
SDK build time35 sec10 sec75% faster
Full test suite~5 min42 sec7x faster
Deployment pipelineN/A4 minUnified & streamlined
Vite build timeBaseline-70-80%Rolldown speedup

Key Takeaways

This overhaul wasn’t about chasing the “new shiny thing.” It was a calculated move to solve real bottlenecks:

  1. Next.js → TanStack Start + Nitro: Faster builds, better control, no vendor lock-in
  2. Rollup → Rolldown: 70-80% faster builds, better code splitting, unified configuration
  3. rslib → tsdown: SDK builds unified on Rolldown, 30-40% faster
  4. Bun test → Vitest: Unified test runner, better tooling, 7x faster execution
  5. TanStack Router everywhere: Consistent routing API across all apps

Unified Stack Philosophy

The real win is consistency. Every package, every app, every test uses the same underlying tools:

Whether you’re working on the wallet, the business dashboard, or the SDK, the developer experience is identical. No context switching, no special cases, just build, test, ship.

Now, our developers can focus on building features, not waiting for builds to complete.

100%