404 Not Found

404 Not Found


nginx

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


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

100%
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

VUE
<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Layout padrão

VUE
<!-- layouts/default.vue -->
<template>
  <div class="layout-default">
    <AppHeader />
    <main class="container">
      <slot />
    </main>
    <AppFooter />
  </div>
</template>

Saída:

TEXT
// Execução Bem-sucedida

(3) ▶Exemplo: Layout com Sidebar

VUE
<!-- 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:

TEXT
// Execução Bem-sucedida

5. Rotas API Core

(1) ▶Exemplo: API de Lista de Produtos

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: API de Detalhes do Produto

TYPESCRIPT
// 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:

TEXT
// Execução Bem-sucedida

6. Implementação das Páginas

(1) ▶Exemplo: Pré-busca SSR para a Homepage

VUE
<!-- 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:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Página de Detalhes do Produto

VUE
<!-- 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:

TEXT
// 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

TYPESCRIPT
// 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

P Por que os dados não atualizam após a homepage ser pré-renderizada?
R Definir 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.
P Por que a página de detalhes do produto às vezes usa SSR e às vezes não?
R Verifique se 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.
P Como uma página 404 é acionada?
R O Nuxt 3 exibe automaticamente uma página 404 quando uma rota não pode ser correspondida. Você também pode lançar um createError({ statusCode: 404 }) no nível da API.
P O que fazer se houver flickering ao trocar layouts?
R Os layouts são re-renderizados ao trocar; você pode envolver o slot em <Transition> para adicionar uma animação de transição, ou usar a configuração pageTransition.
P Como os dados mock serão substituídos no ambiente de produção?
R Na Fase 2, o Prisma substituirá os mocks. O formato de resposta da API atual permanecerá consistente; mais tarde, bastará modificar a implementação da API do servidor.
P Como verificar se o SSR está funcionando?
R Use a função "Ver Código-fonte" do navegador (Ctrl+U). Se o HTML incluir nomes e preços dos produtos, o SSR está funcionando. Se houver apenas uma div vazia, é CSR.

📖Resumo


📝Exercícios

  1. 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.
  2. Exercício Avançado (Dificuldade: ⭐⭐): Adicione um recurso de filtro de categoria de produtos, e use watch e useFetch na página de lista de produtos para atualizar automaticamente a página ao trocar de categorias.
  3. 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.

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%