Nuxt Server Routes vs. API Routes: Choosing the Right Approach
Nuxt 3 blurs the line between frontend and backend by letting you create server logic directly inside your Nuxt app. That’s powerful—but it also raises a common question: should you use Nuxt server routes or a separate API service?
What are Nuxt server routes?
Nuxt server routes are backend endpoints defined inside your Nuxt app, powered by Nitro. They live in the /server directory and are deployed along with your frontend.
Basic example
Create a file at server/api/hello.get.ts:
export default defineEventHandler((event) => {
return { message: 'Hello from Nuxt server route!' }
})
This automatically exposes a GET endpoint at /api/hello in your app.
Key characteristics
- Same codebase: Frontend and backend live together.
- SSR‑friendly: Easy to call from server‑side code (e.g., in
asyncDataor server composables). - Nitro‑powered: Can run on serverless, edge, or traditional Node environments.
- Great for app‑specific logic: Perfect for logic tightly coupled to your Nuxt app.
What are API routes (separate backend)?
By “API routes” here, we mean a separate backend service—for example, a Node/Express app, a Laravel API, a Go service, or a hosted backend like Supabase or Firebase. Your Nuxt app becomes a client of that API.
Typical architecture
- Nuxt app: Handles UI, SSR, routing, and some light data orchestration.
- Backend API: Exposes REST/GraphQL endpoints, handles business logic, auth, and data persistence.
In this setup, Nuxt calls the API over HTTP, just like any other client.
Comparing Nuxt server routes and external API routes
1. Architecture and complexity
- Nuxt server routes: Simpler architecture—one repo, one deployment pipeline. Great for small to medium apps or teams that want to move fast.
- External API: More moving parts—separate repos, deployments, and infrastructure. Better for large systems, multiple clients, or strict separation of concerns.
2. Performance and latency
- Nuxt server routes: Often lower latency for SSR because the server logic runs “next to” the rendering process. No extra network hop inside your own app.
- External API: Adds a network hop, but can scale independently and be optimized specifically for API traffic.
3. Scaling and team boundaries
- Nuxt server routes: Frontend and backend scale together. This is convenient, but can become limiting if backend load grows faster than frontend traffic.
- External API: Backend can scale independently, be versioned separately, and be owned by a different team.
4. Reusability across clients
- Nuxt server routes: Primarily serve your Nuxt app. You can expose them publicly, but that’s not always the intent.
- External API: Designed to be consumed by multiple clients (web, mobile, other services) from day one.
5. Security and exposure
- Nuxt server routes: Great for “backend‑for‑frontend” patterns—hide third‑party keys, normalize responses, and keep sensitive logic off the client.
- External API: Needs a more formal security model (tokens, scopes, rate limiting) because it’s often a shared, long‑lived service.
When to use Nuxt server routes
Nuxt server routes shine when your backend needs are closely tied to your Nuxt app and you want to move quickly.
Great use cases
- Prototyping and MVPs: Build full‑stack features in a single repo with minimal overhead.
- Backend‑for‑frontend (BFF): Wrap third‑party APIs, handle auth tokens, and shape data specifically for your UI.
- Small to medium products: Internal tools, dashboards, or SaaS apps where Nuxt is the primary client.
- SSR‑optimized data fetching: Fetch data server‑side without leaving the Nuxt/Nitro environment.
Example: BFF wrapper around a third‑party API
server/api/weather.get.ts:
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const city = query.city as string
const apiKey = process.env.WEATHER_API_KEY
const url = `https://api.example.com/weather?city=${city}&key=${apiKey}`
const data = await $fetch(url)
return { city, temperature: data.temp, condition: data.condition }
})
Your frontend only calls /api/weather?city=Paris and never sees the external API key or raw response shape.
When to use a separate API backend
A dedicated API service makes more sense when your system needs to outgrow a single Nuxt app.
Great use cases
- Multiple clients: Web, mobile, partner integrations, or internal services all consuming the same API.
- Heavy business logic: Complex domain rules, workflows, or long‑running processes.
- Strict organizational boundaries: Backend and frontend owned by different teams with separate release cycles.
- Legacy or existing APIs: Nuxt is just one of many consumers of an existing backend.
Example: Nuxt consuming an external API
In a Nuxt page or composable:
const { data, error } = await useFetch('https://api.example.com/v1/posts', {
headers: {
Authorization: `Bearer ${token}`
}
})
Nuxt stays focused on rendering and UX, while the external API owns the data and business rules.
Hybrid approach: the sweet spot for many teams
In practice, many real‑world apps use a hybrid approach:
- External API: Core business logic, data models, and long‑lived endpoints.
- Nuxt server routes: BFF layer that adapts the external API to the needs of the Nuxt app.
This gives you the best of both worlds: a reusable backend plus a Nuxt‑specific layer optimized for your UI and SSR.