Comprehensive Exercises — Getting Started
Now that we've completed the five lessons in Phase 1, it's time to tie everything together. Charlie has asked the team to build a working basic version of MegaShop—including a home page, category pages, and product detail pages, as well as SSR pre-fetching and proper layout switching. Alice will act as a customer to test the user experience.
1. What You'll Learn
- Build a complete MegaShop page framework
- Implement automatic switching between the "default" and "sidebar" layouts
- Use
useFetchto preload product data from the API and render it - Handling Dynamic Routing Parameters and 404 Pages
- Code Review: Correctness of SSR Data Hydration and auto-import
2. A True Story About a Team
(1) Pain Point: Inability to Synthesize Disparate Knowledge
Alice has learned about routing, components, and data fetching, but doesn't know how to put them together into a complete project. The page Bob wrote has no layout, and the page flickers after the product data loads. Charlie needs an MVP to demonstrate to investors.
(2) Comprehensive Solutions for Real-World Problems
Integrate all the concepts from Phase 1 into a single project: file routing + layout system + useFetch SSR prefetching + automatic component import, and build a working e-commerce platform from scratch.
(3) Benefits: From Parts to Products
By the end of this chapter, Alice will have a working basic version of MegaShop—with four pages, two layouts, server-side rendering (SSR) with pre-fetched product data, and SEO-friendly HTML output.
3. Project Planning
(1) MegaShop Basic Edition Architecture
flowchart TB
A[MegaShop Phase 1] --> B[Pages]
A --> C[Layouts]
A --> D[Components]
A --> E[API Routes]
B --> B1[/ → Homepage]
B --> B2[/products → Product List]
B --> B3[/products/[id] → Product Detail]
B --> B4[/about → About Page]
C --> C1[default → Header + Footer]
C --> C2[sidebar → Admin Sidebar]
D --> D1[AppHeader]
D --> D2[AppFooter]
D --> D3[ProductCard]
E --> E1[GET /api/products]
E --> E2[GET /api/products/:id]
(2) Page and Routing Design
| URL | Page File | Layout | Features |
|---|---|---|---|
| / | pages/index.vue | default | Home: Featured Products |
| /products | pages/products/index.vue | default | Product List (SSR Prefetch) |
| /products/[id] | pages/products/[id].vue | default | Product Details (SSR Prefetch) |
| /about | pages/about.vue | default | About Page |
| /admin | pages/admin/index.vue | sidebar | Admin Dashboard |
| 404 | Page Not Found | default | Custom 404 |
4. Building the Page Structure
(1) ▶ Example: app.vue root component
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
Output:
// Execution Successful
(2) ▶ Example: default layout
<!-- layouts/default.vue -->
<template>
<div class="layout-default">
<AppHeader />
<main class="container">
<slot />
</main>
<AppFooter />
</div>
</template>
Output:
// Execution Successful
(3) ▶ Example: Sidebar Layout
<!-- layouts/sidebar.vue -->
<template>
<div class="layout-sidebar">
<AppHeader />
<div class="main-wrapper">
<aside class="sidebar">
<h3>Admin Panel</h3>
<nav>
<NuxtLink to="/admin">Dashboard</NuxtLink>
<NuxtLink to="/admin/products">Products</NuxtLink>
<NuxtLink to="/admin/orders">Orders</NuxtLink>
</nav>
</aside>
<main class="content">
<slot />
</main>
</div>
</div>
</template>
Output:
// Execution Successful
5. Core API Routing
(1) ▶ Example: Product List API
// 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: `Product ${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
}
})
Output:
// Execution Successful
(2) ▶ Example: Product Details API
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const product = {
id,
name: `Premium Product ${id}`,
price: 299.99,
image: `/images/product-${id}.webp`,
category: 'electronics',
inStock: true,
description: `High-quality product #${id} with premium features.`,
reviewCount: Math.floor(Math.random() * 5 * 1000),
rating: 4.5
}
if (id > 100) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
return product
})
Output:
// Execution Successful
6. Page Implementation
(1) ▶ Example: SSR Prefetching for the Home Page
<!-- pages/index.vue -->
<template>
<div class="homepage">
<section class="hero">
<h1>MegaShop</h1>
<p>Over 1 million products, shipped worldwide</p>
<NuxtLink to="/products" class="cta">Shop Now</NuxtLink>
</section>
<section class="featured">
<h2>Featured Products</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 - Premium E-Commerce' })
</script>
Output:
// Execution Successful
(2) ▶ Example: Product Details Page
<!-- 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 }} reviews · {{ product.rating }} stars</p>
<button @click="addToCart">Add to Cart</button>
</div>
</div>
<div v-else class="not-found">
<h1>Product Not Found</h1>
<NuxtLink to="/products">Back to Products</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` : 'Not Found')
})
const cart = useState<any[]>('cart', () => [])
function addToCart() {
if (product.value) cart.value.push(product.value)
}
</script>
Output:
// Execution Successful
7. Code Review Checklist
(1) SSR Data Hydration Check
| Check Item | Correct Approach | Common Mistakes |
|---|---|---|
| Using useFetch | await useFetch() Waiting for data |
Forgetting to use await results in a pending state |
| SSR View Source Code | HTML Contains Product Data | Only Empty <div> |
| Payload Passed | Client Does Not Repeat Requests | Duplicate API Requests in Network |
| route.params | Retrieved at the top level of setup |
Retrieved in onMounted (this hook is not available in SSR) |
(2) Auto-import Validity Check
| Check Item | Correct Approach | Common Mistakes |
|---|---|---|
| Component References | <ProductCard /> Auto-import |
Manually import components |
| Composable | useFetch() Use directly |
Manually import useFetch |
| useState | useState('key') Use directly |
Import manually from 'nuxt/app' |
| Component Name | Reference Based on Directory Prefix Rules | Component Name Does Not Match File Path |
8. Comprehensive Example: MegaShop Basic Edition nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
app: {
head: {
title: 'MegaShop',
meta: [
{ name: 'description', content: 'Premium e-commerce with over 1 million products' }
]
}
},
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
components: [{ path: '~/components', pathPrefix: false }],
routeRules: {
'/': { prerender: true },
'/products': { swr: 60 },
'/products/**': { swr: 3600 },
'/admin/**': { ssr: false }
}
})
❓ FAQ
prerender: true generates static HTML during the build process, so the data will not update automatically. You'll need to rebuild or switch to the swr strategy.useFetch is correctly used with await. If useFetch is inside a conditional statement, SSR may skip it. Make sure to call it at the top level of setup.createError({ statusCode: 404 }) at the API level.<Transition> to add a transition animation, or use the pageTransition configuration.📖 Summary
- MegaShop Basic Edition includes four core pages: the home page, product list, product details, and the "About" page
- Two layouts: "default" (front end) and "sidebar" (admin panel), which can be toggled using
definePageMeta - useFetch SSR pre-fetching ensures that the HTML includes product data, making it SEO-friendly
- The server API uses mock data; Prisma will be used in subsequent phases
- Verify SSR: Check that the source code contains data, that there are no duplicate requests in the Network tab, and that auto-import returns no errors
📝 Exercises
- Basic Exercise (Difficulty: ⭐): Complete all pages of the MegaShop Basic Edition and ensure that all routes are accessible.
- Advanced Exercise (Difficulty: ⭐⭐): Add a product category filter feature, and use
watchanduseFetchon the product list page to automatically refresh the page when switching categories. - Challenge (Difficulty: ⭐⭐⭐): Add an admin/products page (sidebar layout + CSR mode), implement a product table display, and compare the differences between CSR and SSR pages when viewing the source code.
---|



