Set Up the Hot Springs Finder
Every framework has a shelving system. In Next.js, everything goes on one bookcase: pages, layouts, API routes, loading states, error boundaries, all in the app/ directory. You know where to reach because you've memorized the system.
Nuxt shelves things differently. Client code on one shelf, server code on another. Layouts get their own section. Components auto-import themselves.
Outcome
Scaffold a Nuxt 4 app with Tailwind CSS and understand how the project structure maps to Next.js.
Fast Track
- Clone the starter repo and install dependencies
- Open the project and compare the file structure to Next.js
- Start the dev server and confirm the home page loads
Hands-on exercise 1.1
Clone the starter repo and get oriented in the Nuxt project structure.
Requirements:
- Clone the starter repo and install dependencies with
pnpm install - Review the file structure, paying attention to
app/,server/, andnuxt.config.ts - Start the dev server with
pnpm devand visithttp://localhost:3000 - Read through
nuxt.config.tsand identify what each option does
Implementation hints:
- The
app/directory is Nuxt 4's way of separating your client-side code from server code. In Next.js, everything lives underapp/too, but in Nuxt,server/is a completely separate directory with its own auto-imports nuxt.config.tsis the equivalent ofnext.config.js, but with a lot more built in. No need for separate Tailwind config files with v4- Nuxt auto-imports Vue utilities (
ref,computed,watch) and its own composables (useFetch,useRoute). No import statements needed for these
Here's how the project structure maps to what you already know:
Nuxt 4 Next.js
───── ───────
app/ app/
pages/ (route files)
components/ components/
layouts/ (layout.tsx files)
composables/ hooks/
assets/ assets/
/public public/ or styles/
server/ app/api/
api/ (route handlers)
routes/ (no equivalent)
data/ (no equivalent)
nuxt.config.ts next.config.js
A few things that might trip you up:
server/is not insideapp/. In Next.js, your API routes live alongside your pages. In Nuxt, the server is a separate world powered by Nitro. It has its own auto-imports, its own utilities, and it doesn't know about Vue.- No
layout.tsxfiles inside page folders. Nuxt uses a dedicatedlayouts/directory. As long asapp.vuewraps<NuxtPage />in<NuxtLayout>,default.vueis applied to every page automatically. - Auto-imports are aggressive. Components in
components/(including subfolders), composables in the top level ofcomposables/(subfolders are not auto-imported), and all Vue utilities are available everywhere without importing them. This feels wrong at first. It's not.
Let's look at the config file that holds it all together:
import tailwindcss from "@tailwindcss/vite";
export default defineNuxtConfig({
compatibilityDate: "2025-05-01",
css: ["~/assets/css/main.css"],
vite: {
plugins: [tailwindcss()],
},
});Note that the ~ in the CSS path is an alias for app.
Nuxt 4 uses the app/ directory for client code and server/ for server code by default. If you've seen older Nuxt 3 projects with everything at the project root, that's the old way. Nuxt 4 enforces the separation out of the box.
The compatibilityDate tells Nuxt which version of breaking-change behavior to use. Think of it like a snapshot: any breaking changes introduced after this date won't affect your app until you update the date.
Tailwind v4 doesn't need a config file. The @tailwindcss/vite plugin handles everything, and main.css just imports it:
@import "tailwindcss";That's it. No tailwind.config.js, no postcss.config.js. If you've been maintaining those files in Next.js projects, this will feel suspiciously easy.
Try It
Start the dev server:
pnpm devVisit http://localhost:3000. You should see the home page with "Find your next soak" and a "Browse Hot Springs" button.
Click "Browse" in the nav. You'll land on /springs with a placeholder message. That's expected. We haven't wired up data fetching yet.
Make sure you ran pnpm install first. If you see a Vue version mismatch error, delete node_modules and pnpm-lock.yaml, then run pnpm install again. Nuxt 4 requires Vue 3.5+.
If port 3000 is taken, Nuxt will automatically try 3001. Check your terminal output for the actual URL.
Commit
git init && git add -A && git commit -m "feat(setup): scaffold Nuxt 4 app with Tailwind"Done-When
pnpm devstarts without errors- Home page loads at
http://localhost:3000with the Hot Springs Finder heading - You can navigate to
/springsand see the placeholder page - You can explain why Nuxt 4 separates
server/fromapp/
Solution
The starter repo already contains the complete setup for this lesson. Your project structure should look like this:
hot-springs-finder/
├── app/
│ ├── app.vue
│ ├── assets/css/main.css
│ ├── components/
│ ├── composables/
│ ├── layouts/
│ │ └── default.vue
│ └── pages/
│ ├── index.vue
│ ├── favorites.vue
│ ├── visited.vue
│ ├── login.vue
│ └── springs/
│ ├── index.vue
│ └── [id].vue
├── server/
│ ├── api/
│ └── data/
│ └── springs.json
├── public/
├── nuxt.config.ts
├── package.json
└── tsconfig.json
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template><template>
<div>
<section>
<h1>
Find your next soak
</h1>
<p>
Discover hot springs around the world. Browse by region, temperature, or
type. Save your favorites and track the ones you've visited.
</p>
<div>
<NuxtLink to="/springs">
Browse Hot Springs
</NuxtLink>
</div>
</section>
</div>
</template>Was this helpful?