Skip to content
ryspark /

Deep dive

Frameworks & stacks we use for full-stack apps.

We pick stacks to fit the product, its goals, and its constraints — not the other way around. Here's how we think about the choices that come up on almost every engagement, and what we reach for when.

The choice is the job

There's no one stack. There's the right stack for this product.

Stack choices compound. The framework you pick on day one shapes which engineers can work on the project, how fast features ship six months in, and what the app costs to run at 100x the traffic. Getting this right matters, and the right answer depends on the product.

A content-driven marketing site has nothing in common with an authenticated trading dashboard, and neither has much in common with a data pipeline that happens to expose a web UI. Below are the frontend and backend options we reach for most often, with the honest trade-offs of each.

Frontend

Vite, Astro, Next.js, and React Router.

Four frameworks cover the vast majority of what we build on the web today. The choice hinges on one question: how much of this product is content, and how much is app?

Vite SPA (React + Vite)

Pure client-rendered single-page apps

The default for internal tools, authenticated dashboards, and apps where SEO isn't a concern. Vite has effectively replaced Create React App as the standard React build tool — fast dev server, instant HMR, and zero ceremony.

Pros

  • Fastest possible dev loop — HMR is near-instant on large codebases
  • No server runtime to operate; ships as static files behind any CDN
  • Minimal framework surface — you own routing, data fetching, and state decisions
  • Pairs naturally with a separate API (Go, Python, or a Node service)

Cons

  • No SEO or social-preview content without extra work — pages render blank until JS loads
  • Initial bundle and time-to-interactive suffer on content-heavy apps
  • You assemble routing (React Router), data fetching, and auth yourself — more decisions up front

Astro

Content sites, marketing, docs, and hybrid content + app

Increasingly the default for marketing sites, documentation, blogs, and any product where content pages dominate. Astro ships zero JavaScript by default and hydrates only the interactive islands — the output is effectively a static site with sprinkles of React, Vue, or Svelte where needed.

Pros

  • Excellent SEO and Core Web Vitals out of the box — real HTML, tiny payloads
  • Framework-agnostic islands — use React, Vue, Svelte, or Solid components in the same project
  • Great authoring story for content-heavy sites (MDX, content collections, typed frontmatter)
  • Static output deploys anywhere — no server runtime required for most use cases

Cons

  • Not the right tool for heavily interactive app surfaces — the island model fights you at some point
  • Smaller ecosystem than Next.js for auth, payments, and ORM integration
  • Server-side rendering for dynamic apps works but is less mature than in dedicated SSR frameworks

Next.js

Full-stack React apps with SEO, SSR, and server components

The most widely used React meta-framework. Good fit when you need the full package: server-rendered pages, API routes, image optimization, middleware, and first-class Vercel or self-hosted deployment. React Server Components have pushed Next into a different shape than it had two years ago — more capable, but with more concepts to learn.

Pros

  • One framework covers pages, APIs, and data loading — less glue to write
  • Mature ecosystem for auth (NextAuth), payments, and edge deployment
  • Server components enable data access without building a separate API for many apps
  • Image, font, and bundle optimization work without configuration

Cons

  • App Router + server components has a steep conceptual curve — caching and invalidation are easy to get wrong
  • Opinionated in ways that can fight you if you want a custom build or routing model
  • Deploying outside Vercel is possible but takes more care (sharp, caching, streaming)
  • Bundle size and hydration cost on complex pages still need attention

Remix / React Router (v7+)

SSR React apps that lean on web standards

Remix merged into React Router v7, so "React Router framework mode" is the modern continuation. It leans on web platform primitives — standard Request/Response, form submissions, and nested routing with per-route loaders and actions. Strong choice for apps that want SSR without Next's server-component model.

Pros

  • Loader/action model maps cleanly to REST and progressive enhancement — forms work without JS
  • Nested routing with per-route data loading avoids waterfalls naturally
  • Runs on any standard JS runtime (Node, Bun, Cloudflare Workers, Deno)
  • Smaller conceptual surface than Next App Router

Cons

  • Smaller ecosystem and fewer turnkey integrations than Next.js
  • Recent v7 consolidation means some docs and tutorials are still catching up
  • No equivalent to React Server Components if that model is what you want

Our default frontend stack

Tailwind, Radix-based components, react-query, and maybe Zustand.

Whichever framework we land on, the layer above it looks roughly the same. These are the choices we make by default because they hold up well as the app grows and they don't lock the team in.

Tailwind CSS

Utility-first styling that stays fast as the project grows. No CSS-in-JS runtime cost, no naming debates, and the design system lives in one config file. Works equally well across Vite, Astro, Next, and React Router.

shadcn/ui + Radix (or a headless library)

Componentry should be modular and owned by the app. Radix gives you accessible, unstyled primitives (dialog, popover, menu, combobox) and shadcn/ui is a pattern for copying styled components into your repo rather than pulling a versioned library. You keep full control over markup, styling, and behavior — no fighting a component library's opinions six months in. Alternatives like Headless UI, Ark UI, or React Aria fill the same role.

TanStack Query (react-query)

The right primitive for server state — caching, revalidation, background refetch, optimistic updates, and request deduping. Using local state (useState, Redux) to cache server data is the single most common mistake we see in React apps; react-query replaces that with a model that actually matches how servers behave.

Zustand (optional, for client state)

When you do need real client state — a wizard, a filter panel, an editor — Zustand is a small, unopinionated store without the Redux ceremony. Most apps need very little of this once server state is handled by react-query; many need none at all.

Tailwind CSS shadcn/ui Radix UI TanStack Query Zustand TypeScript

Backend

Node, Go, or Python — chosen for the workload.

The backend language should match the work, the team, and the things that will run next to it. We're fluent in all three and switch between them without romance.

Node.js (Express, Koa, Fastify)

The default when the team is already writing JavaScript, when you want to share types or code between client and server, or when the workload is I/O-bound (proxying APIs, real-time, orchestration). Express is still the lingua franca; Koa is a leaner alternative with modern async semantics; Fastify is the pick when performance and JSON schema validation actually matter.

Pros

  • Share types, validation (Zod), and code with a TypeScript frontend
  • Huge ecosystem for auth, payments, queues, and SaaS SDKs
  • Excellent for WebSocket / SSE / streaming workloads

Cons

  • CPU-bound workloads (image/video processing, heavy compute) need a worker model or a different language
  • Long-running processes need care around memory and leaks
  • Express in particular shows its age — middleware patterns are easy to misuse

Go (net/http, chi, Gin)

The right call when we need predictable latency, easy deployment (single static binary), concurrency without a runtime, or a service that will outlive everything around it. The standard library alone is production-grade for HTTP; chi adds ergonomic routing and middleware without Go frameworks' heavier baggage.

Pros

  • Compiles to a single static binary — trivial to containerize or drop on a VM
  • Excellent concurrency primitives (goroutines, channels) and predictable GC
  • Great for infrastructure-adjacent services: proxies, queues, schedulers, CLI tools
  • Strong standard library — you often don't need a framework at all

Cons

  • Less type expressiveness than TypeScript or Python with types — some patterns are verbose
  • Smaller ecosystem for niche SaaS SDKs than Node or Python
  • Iteration speed is slower than a scripting language during exploratory work

Python (Django, FastAPI, Celery, Poetry)

The pick when the work is data-heavy, ML-adjacent, or sits next to existing Python code. Django is unmatched for admin-driven CRUD products and batteries-included web apps; FastAPI is our default for modern, typed APIs and for anything backing AI workloads. Celery handles background jobs and scheduled tasks. We manage dependencies with Poetry (or uv on newer projects) — never a bare requirements.txt.

Pros

  • Django gives you admin, ORM, auth, and migrations on day one — enormous for internal tools
  • FastAPI's type-first design pairs cleanly with Pydantic and with AI/LLM code
  • Best-in-class ecosystem for data, ML, and scientific workloads
  • Celery is a known quantity for background jobs at scale

Cons

  • Runtime performance is the lowest of the three — not the pick for CPU-bound hot paths
  • Packaging and deployment have historically been painful (Poetry / uv fix most of this)
  • Async story is solid but fragmented — mixing sync and async code takes care

How we choose

Product first, stack second.

Before we pick a framework we want to know what the product is, who it's for, what the traffic and latency targets look like, what the team who'll maintain it already knows, and what has to integrate with it. Those answers narrow the choices quickly.

A marketing site with a blog is almost always Astro. An internal tool with heavy interactivity and no SEO needs is almost always a Vite SPA. A consumer product that needs SSR, image optimization, and a path to server-side data is usually Next.js. A server-rendered app that wants progressive enhancement and web-standard primitives is Remix / React Router.

On the backend, we reach for Go when latency, deployment simplicity, or concurrency matter; Python when the work is data- or ML-adjacent; and Node when sharing code with the frontend or speaking to a lot of SaaS APIs is the main constraint. None of these are the "best" backend. They're all the best backend for something specific.

If you'd like a second opinion on your stack or help picking one for a new build, that's one of the most common ways teams start working with us.

Talk stack with us

Not sure what to build on? We'll help you decide.