💻 Exemplos: Capítulo 18: Server Routes, Server-Side Rendering (SSR) e SSG no Nuxt
Especialização em Front-End com Vue • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 18 — Rotas de API Nitro Server e Configuração Híbrida de Renderização (SSR / SSG)
Este exemplo demonstra a criação de uma API Backend interna no Nuxt 3 através do Nitro Engine (server/api/), manipulação segura de headers e query params com defineEventHandler, e o consumo direto na camada de frontend com $fetch.
1. Endpoint Server-Side Nitro: server/api/cluster/metrics.get.ts
// server/api/cluster/metrics.get.ts
import { defineEventHandler, getQuery, createError } from 'h3';
interface ClusterMetricResponse {
timestamp: string;
clusterId: string;
loadAverage: number;
memoryUsedPercentage: number;
activePods: number;
}
export default defineEventHandler(async (event): Promise<ClusterMetricResponse> => {
// 1. Extração segura de parâmetros de query string
const query = getQuery(event);
const clusterId = (query.cluster as string) || 'default-us-east';
// 2. Simulação de autenticação de segredos server-only
const apiKey = event.node.req.headers['x-internal-secret'];
const runtimeConfig = useRuntimeConfig();
// Em ambiente server, runtimeConfig.apiSecret é protegido contra vazamento no cliente
console.log(`[Nitro Server] Atendendo requisição para o cluster: ${clusterId}`);
// Simulação de latência de banco de dados
await new Promise((resolve) => setTimeout(resolve, 300));
return {
timestamp: new Date().toISOString(),
clusterId,
loadAverage: Number((Math.random() * 2 + 1.2).toFixed(2)),
memoryUsedPercentage: Number((Math.random() * 30 + 55).toFixed(1)),
activePods: Math.floor(Math.random() * 40) + 120
};
});
2. Configuração de Cache no Edge: nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
routeRules: {
// Rotas estáticas pré-renderizadas no build (SSG puro)
'/about': { prerender: true },
'/docs/**': { prerender: true },
// Rota SSR sob demanda com cache SWR de 60 segundos no Edge
'/dashboard/**': { swr: 60 },
// Endpoint de API com cache do servidor por 10 segundos
'/api/cluster/**': { cache: { maxAge: 10 } },
// SPA puro no cliente para área restrita com autenticação
'/admin/**': { ssr: false }
}
});
3. Componente Vue Consumidor: pages/cluster-status.vue
<!-- pages/cluster-status.vue -->
<script setup lang="ts">
import { ref } from 'vue';
const selectedCluster = ref<string>('us-east-prod');
// $fetch é o cliente HTTP nativo universal do Nuxt (baseado em ofetch)
const { data: metrics, pending, refresh } = await useAsyncData(
'cluster-metrics',
() => $fetch('/api/cluster/metrics', {
query: { cluster: selectedCluster.value }
}),
{ watch: [selectedCluster] } // Dispara novo fetch automaticamente se o cluster mudar
);
</script>
<template>
<div class="max-w-xl mx-auto p-6 bg-white border rounded-2xl shadow-sm space-y-6">
<div class="flex items-center justify-between border-b pb-4">
<div>
<h2 class="text-xl font-bold text-slate-800">Nitro Server API & SSR</h2>
<p class="text-xs text-slate-500">Endpoints server-only embutidos no Nuxt 3</p>
</div>
<button
@click="() => refresh()"
class="text-xs px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-semibold"
>
{{ pending ? 'Consultando...' : 'Atualizar Dados' }}
</button>
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Selecionar Cluster:</label>
<select
v-model="selectedCluster"
class="w-full border px-3 py-2 rounded-lg text-sm bg-white"
>
<option value="us-east-prod">US-East-Prod (Principal)</option>
<option value="sa-east-backup">SA-East-Backup (São Paulo)</option>
<option value="eu-west-edge">EU-West-Edge (Frankfurt)</option>
</select>
</div>
<div v-if="metrics" class="grid grid-cols-3 gap-3">
<div class="p-3 bg-slate-50 border rounded-xl text-center">
<span class="text-[11px] text-slate-400 font-semibold uppercase">Load Avg</span>
<div class="text-xl font-black text-indigo-600 mt-1">{{ metrics.loadAverage }}</div>
</div>
<div class="p-3 bg-slate-50 border rounded-xl text-center">
<span class="text-[11px] text-slate-400 font-semibold uppercase">Memória</span>
<div class="text-xl font-black text-emerald-600 mt-1">{{ metrics.memoryUsedPercentage }}%</div>
</div>
<div class="p-3 bg-slate-50 border rounded-xl text-center">
<span class="text-[11px] text-slate-400 font-semibold uppercase">Pods Ativos</span>
<div class="text-xl font-black text-amber-600 mt-1">{{ metrics.activePods }}</div>
</div>
</div>
<div v-if="metrics" class="text-[11px] font-mono text-slate-400 text-right">
Timestamp do Servidor Nitro: {{ metrics.timestamp }}
</div>
</div>
</template>
🖥️ Saída / Comportamento Esperado
Chamada HTTP de Teste via Terminal:
$ curl http://localhost:3000/api/cluster/metrics?cluster=sa-east-backup
Resposta JSON:
{
"timestamp": "2026-09-28T04:10:00.123Z",
"clusterId": "sa-east-backup",
"loadAverage": 1.84,
"memoryUsedPercentage": 68.2,
"activePods": 142
}
Comportamento no Frontend:
Ao trocar a opção no select para "EU-West-Edge":
A opção { watch: [selectedCluster] } em useAsyncData detecta a alteração reativa.
Dispara automaticamente a nova consulta à API Nitro com query param atualizado.
Os 3 cards são recalculados sem recarregar a página.
🔍 Destaques da Implementação
- Zero Configuração de Servidor: Arquivos dentro de
server/api/tornam-se automaticamente endpoints HTTP tipados sem necessidade de Express ou Fastify externo. routeRulesHíbridas: Permite misturar no mesmo projeto rotas SSG (estáticas), SSR puro, SPA desabilitado e SWR com cache na CDN.- Segurança de Variáveis: O
runtimeConfigdo Nitro nunca expõe senhas de banco ou chaves privadas ao bundle JavaScript do cliente.