← Blog

Why We Don't Build on Webflow Anymore: Our Path to Vue, Nuxt and Sanity

How the studio went from Webflow to React, Next and Sanity to Vue, Nuxt and Sanity. What each migration solved, what bit us before we made the call and why the third stack is the one we are staying on.

Three stack logos in sequence marking the studio's two migrations: Webflow, React and Nuxt.

Julian and I started the Good Fella site on Webflow and switched to React before we shipped a single page. Webflow lets two people work in the same project but not in separate versions of it. There's no branch, no PR, no review surface where in-progress work can live while the rest of the site keeps moving. When Julian renamed a class on the home page hero while I was deep in a section that consumed it, my section shifted under me in real time.

Sanity came in with the React rebuild and stayed. React didn't, because of the kind of motion work the studio sells.

What followed was two migrations and here's what each one bought.

What else was waiting

Parallel work was the visible wall but two more were queued up behind it.

The CMS is the second wall. Webflow's collections work fine for small marketing sites, but they start pushing back the moment the content needs real structure: items that reference other items, fields that only exist in certain cases, relationships that would normally be a join. You can work around it by flattening the data into something Webflow accepts but the flattening is yours forever.

The third wall is the export. Webflow lets you export the site as code, which sounds like a fallback for when you outgrow the builder. The catch is that the export is a one-time snapshot of your project at the moment you hit the button. There's no import-back and the Webflow project never learns your exported code exists. Once you've changed a line of that exported code, your only choices are to freeze the Webflow side at the export point or to keep working in Webflow and overwrite your changes every time you re-export. There's no merge. The "we'll just export it if we have to" plan is a switch you can only flip once.

Sanity earned its seat

Sanity is the one part of the stack we picked twice. We brought it in during the React rebuild of the Good Fella site. We didn't reach for an alternative when we moved to Vue. The reason is the same reason React didn't survive the second migration: how it composes with code.

Sanity schemas are TypeScript files in your repo. You add a field by editing one and committing. The Studio is itself a React app, so a custom field is a React component you write yourself. GROQ queries sit in the same files as the components reading them. There's no separate dashboard where the schema lives, getting out of sync with the code that depends on it. Renaming a field is a code change with a diff and a reviewer.

When the same content model has to live on more than one surface, schema-in-the-repo is the only shape that scales.

The React stop

The Good Fella site moved off Webflow into React, Next and Sanity and is still on it today. What pushed us to the next migration wasn't our own site. It was the first serious motion build on top of the React stack.

The work the studio sells is choreographed motion. Scroll-driven scenes, page transitions that don't look like page transitions, GSAP timelines tweening reactive state that the rest of the page reads. We've used GSAP for that work since the studio existed and stayed with it through every framework migration, so this isn't "switch the animation library and the framework gets easier." React's version of GSAP work is doable but it's consistently more code than it needs to be, which adds up across a project.

The cleanest example is the page transition. Next's App Router doesn't have a built-in primitive that composes arbitrary entrance and exit animations onto the route subtree. You can wire it up with framer-motion's AnimatePresence around the route children or with the Suspense and view-transitions dance. You'll end up building the abstraction yourself though and the abstraction tends to leak into every page that participates in it.

Nuxt has the same surface in a single prop:

Component.vue
<!-- app.vue -->
<NuxtPage :transition="{ name: 'page', mode: 'out-in' }" />

The route subtree fades through out-in, you drop into GSAP from the lifecycle hooks and the choreographed-transition surface from the Crystal Peak Security build is one prop, one composable and a named CSS transition. The persistent-scene write-up shows the GSAP side of that hook from a real project. The Nuxt side is the prop.

The second place React added friction was reactive state inside a GSAP timeline. The most common shape we write is a tween against a reactive object that the rest of the page reads. In Vue it's close to free:

script.ts
// composables/useFrustum.ts
import { reactive } from 'vue'
import gsap from 'gsap'

const frustum = reactive({ x: 0, y: 0 })

export function tweenTo(target: { x: number; y: number }) {
  gsap.to(frustum, {
    x: target.x,
    y: target.y,
    duration: 1.2,
    ease: 'power2.inOut',
    overwrite: 'auto',
  })
}

Anything in the template reading frustum.x re-renders as the tween runs. No subscription, no observer, no useEffect. The reactive object is the source of truth and GSAP is just the thing moving it.

In React the same shape needs useGSAP, a useRef for the timeline so renders don't recreate it, a useEffect for cleanup and an external store or a useState mirror if the rest of the component should re-render with the tween. None of it is hard. It's consistently more places to look where the Vue version is one composable. GSAP doesn't compose cleanly with React's reactivity and we wrote the same plumbing on every project until we stopped.

What the third stack actually does

Vue, Nuxt and Sanity is the stack we use for new client builds and for everything inside the Good Fella Lab. This blog, the editions, the proposal viewer and the asset-fella surface. The Good Fella site itself is still on React, Next and Sanity. Migrating it is on the list. It isn't urgent because it's a working site and nothing about the React side actively hurts there.

The win that took us through both migrations is the one worth naming flatly: the stack lets two people work in parallel without negotiating turns, encodes the schema in the repo and gets out of the way of the kind of motion the studio sells. The Webflow wall was about parallel work and the React stop was about motion. Both are about getting the stack to stop being a thing the studio works against.

Every stack we've left behind was the right call when we picked it and got harder to live with as the studio changed. The current one is the one we haven't outgrown yet.