Exercícios Completos — Iniciação
Agora que completamos as cinco lições da Fase 1, é hora de juntar tudo. Charlie pediu à equipe para construir uma versão básica funcional da MegaShop—incluindo uma homepage, páginas de categorias e páginas de detalhes de produtos, bem como pré-busca SSR e troca adequada de layout. Alice atuará como cliente para testar a experiência do usuário.
1. O Que Você Vai Aprender
- Construir um framework completo de páginas da MegaShop
- Implementar troca automática entre os layouts "default" e "sidebar"
- Usar
useFetchpara pré-carregar dados de produtos da API e renderizá-los - Tratamento de Parâmetros de Rotas Dinâmicas e Páginas 404
- Code Review: Correção da Hidratação de Dados SSR e auto-import
2. Uma História Real de uma Equipe
(1) Dor: Incapacidade de Sintetizar Conhecimentos Dispersos
Alice aprendeu sobre rotas, componentes e busca de dados, mas não sabe como juntá-los em um projeto completo. A página que Bob escreveu não tem layout, e a página pisca após os dados dos produtos carregarem. Charlie precisa de um MVP para apresentar aos investidores.
(2) Soluções Completas para Problemas do Mundo Real
Integre todos os conceitos da Fase 1 em um único projeto: roteamento por arquivos + sistema de layouts + pré-busca SSR useFetch + importação automática de componentes, e construa uma plataforma de e-commerce funcional do zero.
(3) Benefícios: Das Partes aos Produtos
Ao final deste capítulo, Alice terá uma versão básica funcional da MegaShop—com quatro páginas, dois layouts, renderização no lado do servidor (SSR) com dados de produtos pré-buscados, e saída HTML amigável para SEO.
3. Planejamento do Projeto
(1) Arquitetura da Edição Básica da MegaShop
flowchart TB
A[MegaShop Fase 1] --> B[Páginas]
A --> C[Layouts]
A --> D[Componentes]
A --> E[Rotas API]
B --> B1[/ → Homepage]
B --> B2[/products → Lista de Produtos]
B --> B3[/products/[id] → Detalhe do Produto]
B --> B4[/about → Página Sobre]
C --> C1[default → Header + Footer]
C --> C2[sidebar → Sidebar Admin]
D --> D1[AppHeader]
D --> D2[AppFooter]
D --> D3[ProductCard]
E --> E1[GET /api/products]
E --> E2[GET /api/products/:id]
(2) Design de Páginas e Rotas
| URL | Arquivo da Página | Layout | Funcionalidades |
|---|---|---|---|
| / | pages/index.vue | default | Home: Produtos em Destaque |
| /products | pages/products/index.vue | default | Lista de Produtos (Pré-busca SSR) |
| /products/[id] | pages/products/[id].vue | default | Detalhes do Produto (Pré-busca SSR) |
| /about | pages/about.vue | default | Página Sobre |
| /admin | pages/admin/index.vue | sidebar | Painel de Administração |
| 404 | Página Não Encontrada | default | 404 Personalizado |
4. Construindo a Estrutura de Páginas
(1) ▶Exemplo: Componente raiz app.vue
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
Saída:
// Execução Bem-sucedida
(2) ▶Exemplo: Layout padrão
<!-- layouts/default.vue -->
<template>
<div class="layout-default">
<AppHeader />
<main class="container">
<slot />
</main>
<AppFooter />
</div>
</template>
Saída:
// Execução Bem-sucedida
(3) ▶Exemplo: Layout com Sidebar
<!-- layouts/sidebar.vue -->
<template>
<div class="layout-sidebar">
<AppHeader />
<div class="main-wrapper">
<aside class="sidebar">
<h3>Painel Admin</h3>
<nav>
<NuxtLink to="/admin">Dashboard</NuxtLink>
<NuxtLink to="/admin/products">Produtos</NuxtLink>
<NuxtLink to="/admin/orders">Pedidos</NuxtLink>
</nav>
</aside>
<main class="content">
<slot />
</main>
</div>
</div>
</template>
Saída:
// Execução Bem-sucedida
5. Rotas API Core
(1) ▶Exemplo: API de Lista de Produtos
// server/api/products/index.get.ts
interface Product {
id: number; name: string; price: number
image: string; category: string; inStock: boolean
}
const mockProducts: Product[] = Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
name: `Produto ${i + 1}`,
price: Math.round(Math.random() * 500 * 100) / 100,
image: `/images/product-${i + 1}.webp`,
category: ['electronics', 'clothing', 'home'][i % 3],
inStock: Math.random() > 0.2
}))
export default defineEventHandler((event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 12
const category = query.category as string
let filtered = mockProducts
if (category) filtered = filtered.filter(p => p.category === category)
const start = (page - 1) * limit
return {
items: filtered.slice(start, start + limit),
total: filtered.length,
page,
limit
}
})
Saída:
// Execução Bem-sucedida
(2) ▶Exemplo: API de Detalhes do Produto
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const product = {
id,
name: `Produto Premium ${id}`,
price: 299.99,
image: `/images/product-${id}.webp`,
category: 'electronics',
inStock: true,
description: `Produto de alta qualidade #${id} com recursos premium.`,
reviewCount: Math.floor(Math.random() * 5 * 1000),
rating: 4.5
}
if (id > 100) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
return product
})
Saída:
// Execução Bem-sucedida
6. Implementação das Páginas
(1) ▶Exemplo: Pré-busca SSR para a Homepage
<!-- pages/index.vue -->
<template>
<div class="homepage">
<section class="hero">
<h1>MegaShop</h1>
<p>Mais de 1 milhão de produtos, entregues mundialmente</p>
<NuxtLink to="/products" class="cta">Comprar Agora</NuxtLink>
</section>
<section class="featured">
<h2>Produtos em Destaque</h2>
<div class="grid">
<ProductCard v-for="p in featured" :key="p.id" :product="p" />
</div>
</section>
</div>
</template>
<script setup lang="ts">
const { data: featured } = await useFetch('/api/products', {
query: { limit: 6 },
transform: (data: any) => data.items
})
useHead({ title: 'MegaShop - E-Commerce Premium' })
</script>
Saída:
// Execução Bem-sucedida
(2) ▶Exemplo: Página de Detalhes do Produto
<!-- pages/products/[id].vue -->
<template>
<div v-if="product" class="product-detail">
<img :src="product.image" :alt="product.name" />
<div class="info">
<h1>{{ product.name }}</h1>
<p class="price">${{ product.price }} USD</p>
<p>{{ product.description }}</p>
<p>{{ product.reviewCount }} avaliações · {{ product.rating }} estrelas</p>
<button @click="addToCart">Adicionar ao Carrinho</button>
</div>
</div>
<div v-else class="not-found">
<h1>Produto Não Encontrado</h1>
<NuxtLink to="/products">Voltar aos Produtos</NuxtLink>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
useHead({
title: computed(() => product.value ? `${product.value.name} - MegaShop` : 'Não Encontrado')
})
const cart = useState<any[]>('cart', () => [])
function addToCart() {
if (product.value) cart.value.push(product.value)
}
</script>
Saída:
// Execução Bem-sucedida
7. Checklist de Code Review
(1) Verificação de Hidratação de Dados SSR
| Item de Verificação | Abordagem Correta | Erros Comuns |
|---|---|---|
| Usar useFetch | await useFetch() Aguardar os dados |
Esquecer de usar await resulta em estado pending |
| SSR Ver Código-fonte | HTML Contém Dados dos Produtos | Apenas <div> vazio |
| Payload Transmitido | Cliente Não Repete Requisições | Requisições API Duplicadas no Network |
| route.params | Obtido no nível superior do setup |
Obtido em onMounted (este hook não está disponível no SSR) |
(2) Verificação de Validade do Auto-import
| Item de Verificação | Abordagem Correta | Erros Comuns |
|---|---|---|
| Referências de Componentes | <ProductCard /> Auto-import |
Importar componentes manualmente |
| Composable | useFetch() Usar diretamente |
Importar useFetch manualmente |
| useState | useState('key') Usar diretamente |
Importar manualmente de 'nuxt/app' |
| Nome do Componente | Referência Baseada nas Regras de Prefixo de Diretório | Nome do Componente Não Corresponde ao Caminho do Arquivo |
8. Exemplo Completo: nuxt.config.ts da Edição Básica da MegaShop
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
app: {
head: {
title: 'MegaShop',
meta: [
{ name: 'description', content: 'E-commerce premium com mais de 1 milhão de produtos' }
]
}
},
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
components: [{ path: '~/components', pathPrefix: false }],
routeRules: {
'/': { prerender: true },
'/products': { swr: 60 },
'/products/**': { swr: 3600 },
'/admin/**': { ssr: false }
}
})
❓Perguntas Frequentes
prerender: true gera HTML estático durante o processo de build, então os dados não serão atualizados automaticamente. Você precisará reconstruir ou mudar para a estratégia swr.useFetch está sendo usado corretamente com await. Se useFetch estiver dentro de uma declaração condicional, o SSR pode pulá-lo. Certifique-se de chamá-lo no nível superior do setup.createError({ statusCode: 404 }) no nível da API.<Transition> para adicionar uma animação de transição, ou usar a configuração pageTransition.📖Resumo
- A Edição Básica da MegaShop inclui quatro páginas core: homepage, lista de produtos, detalhes do produto e a página "Sobre"
- Dois layouts: "default" (front-end) e "sidebar" (painel admin), que podem ser alternados usando
definePageMeta - Pré-busca SSR useFetch garante que o HTML inclua dados dos produtos, tornando-o amigável para SEO
- A API do servidor usa dados mock; Prisma será usado nas fases subsequentes
- Verificar SSR: Conferir que o código-fonte contém dados, que não há requisições duplicadas na aba Network, e que o auto-import não retorna erros
📝Exercícios
- Exercício Básico (Dificuldade: ⭐): Complete todas as páginas da Edição Básica da MegaShop e garanta que todas as rotas estejam acessíveis.
- Exercício Avançado (Dificuldade: ⭐⭐): Adicione um recurso de filtro de categoria de produtos, e use
watcheuseFetchna página de lista de produtos para atualizar automaticamente a página ao trocar de categorias. - Desafio (Dificuldade: ⭐⭐⭐): Adicione uma página admin/products (layout sidebar + modo CSR), implemente uma exibição em tabela de produtos, e compare as diferenças entre páginas CSR e SSR ao visualizar o código-fonte.
---|



