404 Not Found

404 Not Found


nginx

Componentes e Layouts

A equipe do Charlie consiste em cinco desenvolvedores, e cada um escreve componentes de forma diferente—alguns importam manualmente, outros registram globalmente, e não há uma convenção de nomenclatura consistente. Alice notou que a página estava carregando muitos componentes desnecessários, o que estava atrasando o carregamento da primeira tela. Bob queria usar um layout diferente para o painel de administração, mas não sabia como trocar.

1. O Que Você Vai Aprender


2. Uma História Real de uma Equipe

(1) Dor: Gerenciamento Desorganizado de Componentes

A equipe de cinco do Charlie trabalha de forma isolada: Alice importa manualmente cada componente, Bob usa registro global, o que faz o tamanho do bundle inflar, e o novo colega não consegue descobrir os nomes dos componentes. Existem três maneiras diferentes de referenciar um único ProductCard em arquivos diferentes.

(2) Solução com a Importação Automática do Nuxt 3

O Nuxt 3 escaneia automaticamente o diretório components/, gera nomes de componentes de acordo com as regras, e garante que todos referenciem de forma consistente:

VUE
<!-- Não é necessário import, apenas use -->
<ProductCard :product="item" />

(3) Benefícios: Padronização + Redução de Tamanho

A equipe padronizou a forma de referenciar componentes; o prefixo "Lazy" garante que apenas componentes visíveis sejam carregados na primeira tela, reduzindo o JavaScript da primeira tela da MegaShop em 40%.


3. Importação Automática de Componentes

(1) Escaneamento de Diretório e Convenções de Nomenclatura

100%
graph TB
    A[components/] --> B[Nível raiz]
    A --> C[Subdiretório]
    A --> D[Diretório aninhado]
    B --> E[ProductCard.vue → ProductCard]
    C --> F[product/Card.vue → ProductCard ①]
    D --> G[shop/product/Card.vue → ShopProductCard]
    style G fill:#ffe,stroke:#f90
⚠️ Nota: ① Após configurar pathPrefix: false, product/Card.vueCard pode resultar em conflito de nomenclatura. Recomendamos manter o prefixo padrão.

(2) Comparação de Convenções de Nomenclatura

Caminho do Arquivo Nome do Componente Padrão pathPrefix: false
components/AppHeader.vue AppHeader AppHeader
components/product/Card.vue ProductCard Card
components/shop/ProductCard.vue ShopProductCard ProductCard
components/admin/user/Table.vue AdminUserTable Table

(1) ▶Exemplo: Importação Automática de Componente

VUE
<!-- components/ProductCard.vue -->
<template>
  <div class="product-card">
    <img :src="product.image" :alt="product.name" />
    <h3>{{ product.name }}</h3>
    <p>${{ product.price }} USD</p>
    <button @click="$emit('add-to-cart', product)">Adicionar ao Carrinho</button>
  </div>
</template>

<script setup lang="ts">
interface Product {
  id: number
  name: string
  price: number
  image: string
}

defineProps<{ product: Product }>()
defineEmits<{ 'add-to-cart': [product: Product] }>()
</script>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Usando o Componente Auto-Importado em uma Página

VUE
<!-- pages/products/index.vue -->
<template>
  <div>
    <h1>Todos os Produtos</h1>
    <div class="grid">
      <!-- ProductCard auto-importado -->
      <ProductCard
        v-for="p in products"
        :key="p.id"
        :product="p"
        @add-to-cart="handleAdd"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
const { data: products } = await useFetch('/api/products')
function handleAdd(product: any) {
  console.log('Adicionado:', product.name)
}
</script>

Saída:

TEXT
// Execução Bem-sucedida

4. Sistema de Layouts

(1) Como o Layout Funciona

100%
graph TB
    A[app.vue] --> B[NuxtLayout]
    B --> C{Layout Atual}
    C -->|default| D[layouts/default.vue]
    C -->|sidebar| E[layouts/sidebar.vue]
    D --> F[NuxtPage - Conteúdo da Página]
    E --> F

(2) A Relação Entre app.vue e Layout

VUE
<!-- app.vue - Componente raiz com suporte a layout -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

(1) ▶Exemplo: Layout Padrão

VUE
<!-- layouts/default.vue -->
<template>
  <div class="layout-default">
    <AppHeader />
    <main class="content">
      <!-- Conteúdo da página renderiza aqui -->
      <slot />
    </main>
    <AppFooter />
  </div>
</template>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Layout com Sidebar

VUE
<!-- layouts/sidebar.vue -->
<template>
  <div class="layout-sidebar">
    <AppHeader />
    <div class="main-wrapper">
      <aside class="sidebar">
        <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

(3) ▶Exemplo: Troca de Layout por Página

VUE
<!-- pages/admin/index.vue -->
<template>
  <div>
    <h1>Painel de Administração</h1>
    <p>Bem-vindo, Bob</p>
  </div>
</template>

<script setup lang="ts">
// Trocar para layout sidebar nesta página
definePageMeta({
  layout: 'sidebar'
})
</script>

Saída:

TEXT
// Execução Bem-sucedida

(3) Comparação de Layout vs. Rotas Aninhadas

Dimensão Layout Rotas Aninhadas
Local de Definição layouts/*.vue pages/parent.vue
Slot de Conteúdo <slot /> <NuxtPage />
Método de Troca definePageMeta Automático (Hierarquia de Rotas)
Recuperação de Dados Não Recomendado Cada Um Busca o Que Precisa
Casos de Uso Layout Geral de Página UI Compartilhada Entre Níveis de Rota

5. Lazy Loading de Componentes

(1) Mecanismo de Lazy Loading

O Nuxt 3 registra automaticamente uma versão "Lazy" de cada componente, carregando o código apenas quando o componente é renderizado pela primeira vez.

(1) ▶Exemplo: Lazy loading com prefixo Lazy

VUE
<template>
  <div>
    <!-- Carregamento imediato - incluído no bundle inicial -->
    <AppHeader />

    <!-- Lazy loaded - code-split, carregado na primeira renderização -->
    <LazyProductCard
      v-for="p in products"
      :key="p.id"
      :product="p"
    />

    <!-- Lazy com v-if - carregado apenas quando a condição é verdadeira -->
    <LazyProductReviewModal
      v-if="showReviewModal"
      :product-id="selectedProductId"
      @close="showReviewModal = false"
    />
  </div>
</template>

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Carregamento Assíncrono com Suspense

VUE
<template>
  <div>
    <Suspense>
      <template #default>
        <LazyHeavyChart :data="chartData" />
      </template>
      <template #fallback>
        <div class="loading">Carregando gráfico...</div>
      </template>
    </Suspense>
  </div>
</template>

Saída:

TEXT
// Execução Bem-sucedida

(2) Comparação de Estratégias de Lazy Loading

Estratégia Implementação Momento do Carregamento Cenários Aplicáveis
Carregamento Imediato <ProductCard /> Quando a página carrega Componentes core na primeira tela
Prefixo Lazy <LazyProductCard /> Na primeira renderização Componentes abaixo da dobra
v-if + Lazy <LazyModal v-if="show" /> Quando a condição é verdadeira Pop-up/Drawer
Suspense Envoltório <Suspense> Quando a operação assíncrona está pronta Um estado "carregando" é necessário

6. Melhores Práticas para Props e Emits

(1) ▶Exemplo: Props e Emits com Segurança de Tipos

VUE
<!-- components/ProductCard.vue -->
<script setup lang="ts">
interface Product {
  id: number
  name: string
  price: number
  image: string
  inStock: boolean
}

// Props com segurança de tipos e valores padrão
const props = withDefaults(
  defineProps<{
    product: Product
    showStock?: boolean
    currency?: string
  }>(),
  {
    showStock: true,
    currency: 'USD'
  }
)

// Emits com segurança de tipos
const emit = defineEmits<{
  'add-to-cart': [product: Product]
  'toggle-wishlist': [productId: number]
}>()
</script>

<template>
  <div class="card">
    <img :src="product.image" :alt="product.name" />
    <h3>{{ product.name }}</h3>
    <p>{{ product.price }} {{ currency }}</p>
    <p v-if="showStock">
      {{ product.inStock ? 'Em Estoque' : 'Esgotado' }}
    </p>
    <button @click="emit('add-to-cart', product)">Adicionar ao Carrinho</button>
    <button @click="emit('toggle-wishlist', product.id)">Favoritar</button>
  </div>
</template>

Saída:

TEXT
// Execução Bem-sucedida

7. Exemplo Completo: Sistema de Componentes de Layout da MegaShop

VUE
<!-- components/AppHeader.vue -->
<template>
  <header class="app-header">
    <NuxtLink to="/" class="logo">MegaShop</NuxtLink>
    <nav>
      <NuxtLink to="/products">Produtos</NuxtLink>
      <NuxtLink to="/categories">Categorias</NuxtLink>
    </nav>
    <div class="user-area">
      <NuxtLink to="/cart">Carrinho ({{ cartCount }})</NuxtLink>
      <NuxtLink v-if="isLoggedIn" to="/admin">Admin</NuxtLink>
      <NuxtLink v-else to="/login">Entrar</NuxtLink>
    </div>
  </header>
</template>

<script setup lang="ts">
const cart = useState<any[]>('cart', () => [])
const cartCount = computed(() => cart.value.length)
const isLoggedIn = useState('isLoggedIn', () => false)
</script>
VUE
<!-- components/AppFooter.vue -->
<template>
  <footer class="app-footer">
    <p>&copy; 2026 MegaShop. Todos os direitos reservados.</p>
    <nav>
      <NuxtLink to="/about">Sobre</NuxtLink>
      <NuxtLink to="/contact">Contato</NuxtLink>
      <NuxtLink to="/privacy">Política de Privacidade</NuxtLink>
    </nav>
  </footer>
</template>
VUE
<!-- layouts/default.vue -->
<template>
  <div class="layout-default">
    <AppHeader />
    <main>
      <slot />
    </main>
    <AppFooter />
  </div>
</template>

❓Perguntas Frequentes

P Por que meu componente não está sendo importado automaticamente?
R Verifique se o arquivo está no diretório components/, se o nome do arquivo começa com letra maiúscula, e se o caminho não foi excluído da configuração components no nuxt.config.ts. Tente reiniciar o servidor dev.
P O prefixo Lazy faz o componente renderizar mais tarde?
R Não. Lazy apenas atrasa o carregamento do código JavaScript do componente; uma vez que o componente renderiza, ele funciona normalmente. Ele realiza code splitting, não renderização adiada.
P O Layout pode buscar dados?
R Tecnicamente sim, mas não é recomendado. Layout é um framework de UI; a recuperação de dados deve ser tratada dentro da página ou de um Composable. Layout é responsável apenas pela estrutura de layout.
P Uma única página pode usar múltiplos layouts?
R Não. Cada página pode especificar apenas um layout usando definePageMeta. Se você precisa de layouts diferentes para seções diferentes, alcance isso combinando componentes.
P O definePageMeta precisa estar no nível superior do script setup?
R Sim. definePageMeta é uma macro do compilador e deve ser chamado diretamente no nível superior do <script setup>; não pode ser colocado dentro de declarações condicionais ou funções.
P Colocar componentes em um subdiretório muito profundo sob components/ afeta a performance?
R Não afeta a performance em tempo de execução, porque o Nuxt 3 apenas inclui componentes que são realmente usados. No entanto, uma hierarquia de diretórios muito profunda pode aumentar a complexidade de nomenclatura; recomendamos limitar a no máximo dois níveis.

📖Resumo


📝Exercícios

  1. Exercício Básico (Dificuldade: ⭐): Crie os componentes AppHeader e AppFooter e use-os no layout padrão.
  2. Exercício Avançado (Dificuldade: ⭐⭐): Crie um layout com sidebar e alterne entre os dois layouts na página admin usando definePageMeta. Compare os resultados dos dois layouts.
  3. Desafio (Dificuldade: ⭐⭐⭐): Use o prefixo Lazy e v-if para implementar um componente de popup de detalhes do produto que carrega apenas quando clicado, e use Suspense para exibir o estado de carregamento.

---|

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%