How Cool Is Nuxt? Building and Deploying Your First Full-Stack App in Minutes

There's something genuinely satisfying about taking a project from your text editor all the way to a live website with just a couple of clicks. Nuxt 3 makes this feel almost magical — or maybe I'm just tired from too many coffees. Either way, you spin up a frontend app, throw in a tiny API endpoint, push to GitHub, and Vercel deploys it before your coffee cools down. Let's be real: it's addictive.

Why does Nuxt enable this workflow? And more importantly, why is it not just another frontend framework pretending to be full-stack? Let's unpack.


The magic of Nuxt 3

Nuxt has always leaned into simplicity, but Nuxt 3? It takes it further. Seriously. Start with this:

npx nuxi init my-nuxt-app
cd my-nuxt-app
npm install
npm run dev

And boom — live app running locally, powered by Vue 3, TypeScript, and server-side rendering from the get-go. Free. No licenses. No hidden fees. You can host it on Vercel, Netlify, S3, or throw it on your own server. Vercel especially: automatic deployments, preview builds for every PR branch, production-ready env vars. Zero setup required.


A small example: the TODO app

Let's build something actually simple: a TODO list. Add tasks, mark them done, store them in a database. I'm using Supabase here, but any DB works. Everything lives in one Nuxt repo — frontend and backend together.

Nuxt handles the frontend via Vue 3's composition API, letting you write clean, reactive components. Backend? That's Nitro, Nuxt's built-in server engine. It gives you API endpoints right next to your app code without needing a separate backend server.

Here's what the frontend looks like:

<template>
  <main class="container">
    <h1>Nuxt TODOs</h1>

    <form @submit.prevent="createTodo">
      <input v-model="newText" placeholder="What to do?" />
      <button type="submit">Add</button>
    </form>

    <ul>
      <li v-for="todo in todos" :key="todo.id">
        <label>
          <input type="checkbox" :checked="todo.done" @change="toggle(todo)" />
          <span :class="{ done: todo.done }">{{ todo.text }}</span>
        </label>
      </li>
    </ul>
  </main>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useTodos } from '~/composables/useTodos';

const { todos, fetchTodos, addTodo, updateTodo } = useTodos();
const newText = ref('');

onMounted(() => fetchTodos());

async function createTodo() {
  if (!newText.value.trim()) return;
  await addTodo({ text: newText.value.trim() });
  newText.value = '';
}

async function toggle(todo: any) {
  await updateTodo({ ...todo, done: !todo.done });
}
</script>

<style scoped>
.container {
  max-width: 680px;
  margin: 40px auto;
}
.done {
  text-decoration: line-through;
}
</style>

This uses a composable — a Nuxt concept that lets you group and reuse logic without bloat.


The composable: useTodos.ts

import { ref } from 'vue';
import { useFetch } from '#app';

export function useTodos() {
  const todos = ref<any[]>([]);

  async function fetchTodos() {
    const { data } = await useFetch('/api/todos');
    todos.value = data.value ?? [];
  }

  async function addTodo(payload: { text: string }) {
    const { data } = await useFetch('/api/todos', {
      method: 'POST',
      body: payload,
    });
    todos.value.push(data.value);
  }

  async function updateTodo(payload: any) {
    const { data } = await useFetch('/api/todos', {
      method: 'POST',
      body: payload,
    });
    todos.value = todos.value.map((t) =>
      t.id === data.value.id ? data.value : t,
    );
  }

  return { todos, fetchTodos, addTodo, updateTodo };
}

We're just hitting /api/todos. Where does that come from? Nitro.


The backend, right inside your app

Nuxt's Nitro server lets you create endpoints without configuration. Drop a file into server/api/ and it automatically becomes an API route.

Using Supabase as the database, here's a GET handler:

// server/api/todos.get.ts
import { getSupabaseClient } from '../db/supabase.server';

export default defineEventHandler(async () => {
  const supabase = getSupabaseClient();
  const { data, error } = await supabase
    .from('todos')
    .select('*')
    .order('id', { ascending: true });
  if (error)
    throw createError({ statusCode: 500, statusMessage: error.message });
  return data;
});

No Express. No routing setup. Adding a POST handler is equally trivial:

// server/api/todos.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  const supabase = getSupabaseClient();

  const { data, error } = await supabase
    .from('todos')
    .insert({ text: body.text, done: false })
    .select()
    .single();
  if (error)
    throw createError({ statusCode: 500, statusMessage: error.message });
  return data;
});

Everything lives in one repo. Nitro handles server logic — locally and on production.


From code to production: GitHub and Vercel

Time to go live:

git init
git add .
git commit -m "feat: initial todo app"
gh repo create my-nuxt-todos --public --source=. --push

Open vercel.com, import your repo, click Deploy. That's it. Vercel detects Nuxt, installs deps, builds, deploys. Every PR creates a preview deployment. Merge to main, and it redeploys automatically.

Continuous delivery without YAML — though you can add CI for tests, linting, version bumps if you want.


Going a bit further: commits, changelogs, and CI/CD

Want your project to feel more professional?

  • Use commitlint with Conventional Commits (feat:, fix:, chore:) for clean history.
  • Use standard-version or semantic-release to auto-generate changelogs and bump versions.
  • Create a feature branch per pull request — Vercel handles previews automatically.

Here's a small workflow:

  1. Create feature/add-todo-priority
  2. Commit with feat: add priority flag to todos
  3. Push, open a PR, Vercel gives you a live preview
  4. Merge — CI updates changelog, tags a version, Vercel redeploys production

Modern full-stack workflow from one framework and one click-deploy platform.


Why this matters

Nuxt isn't just about rendering pages. It gives developers a single place to build everything — from composables to backend APIs. With Nitro, Vercel, and tools like Supabase, you go from idea to production in an afternoon.

No DevOps setup required. No server management. You write code.

The best part? It's just Vue under the hood — readable, writable, flexible.

Next time you have a small idea, don't overthink it. Run npx nuxi init, push to GitHub, deploy to Vercel, let Nuxt handle the rest.


TL;DR: Nuxt + Nitro + Vercel = full-stack freedom. One repo, one framework, infinite possibilities.