Prerequisites
- Basic understanding of JavaScript π
- TypeScript installation β‘
- VS Code or preferred IDE π»
What you'll learn
- Understand the concept fundamentals π―
- Apply the concept in real projects ποΈ
- Debug common issues π
- Write type-safe code β¨
π― Introduction
Welcome to the exciting world of Nuxt 3 with TypeScript! π In this comprehensive guide, weβll explore how to build powerful, full-stack Vue applications with rock-solid type safety.
Youβll discover how Nuxt 3βs TypeScript integration can transform your Vue development experience. Whether youβre building SSR applications π, static sites π, or hybrid applications π, mastering TypeScript with Nuxt 3 is essential for creating robust, maintainable projects.
By the end of this tutorial, youβll feel confident building type-safe Nuxt 3 applications! Letβs dive in! πββοΈ
π Understanding Nuxt 3 with TypeScript
π€ What is Nuxt 3?
Nuxt 3 is like a supercharged Vue.js framework π. Think of it as Vue.js with built-in superpowers that handles server-side rendering, static generation, and full-stack capabilities automatically.
In TypeScript terms, Nuxt 3 provides automatic type inference, zero-config TypeScript support, and comprehensive type safety across your entire application. This means you can:
- β¨ Get automatic type inference for your pages and components
- π Enjoy zero-config TypeScript setup
- π‘οΈ Have type safety from frontend to backend
- π§ Use powerful auto-imports with full type support
π‘ Why Use Nuxt 3 with TypeScript?
Hereβs why developers love this combination:
- Zero Configuration π§: TypeScript works out of the box
- Auto-imports β‘: Composables and utilities with full type support
- Full-stack Types π: Shared types between client and server
- Developer Experience π»: Incredible IDE support and autocomplete
- Performance π: Optimized builds with type checking
Real-world example: Imagine building an e-commerce platform π. With Nuxt 3 + TypeScript, you get type safety from your API routes to your Vue components, ensuring your product data is always correctly typed!
π§ Basic Syntax and Usage
π Setting Up a New Project
Letβs create your first Nuxt 3 TypeScript project:
# π Create a new Nuxt 3 project
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
# π¦ Install dependencies
npm install
# π― TypeScript is already included!
npm run dev
π‘ Pro Tip: Nuxt 3 automatically detects TypeScript and sets everything up for you! No manual configuration needed.
ποΈ Project Structure
Your Nuxt 3 TypeScript project looks like this:
my-nuxt-app/
βββ π components/ # Auto-imported Vue components
βββ π composables/ # Auto-imported composables
βββ π pages/ # File-based routing
βββ π server/ # Server-side API routes
βββ π types/ # Your TypeScript types
βββ π nuxt.config.ts # Configuration file
βββ π app.vue # Root component
π¨ Basic Page Component
Hereβs a simple typed page:
// π pages/index.vue
<template>
<div>
<h1>Welcome {{ user.name }}! π</h1>
<p>You have {{ todos.length }} todos π</p>
<button @click="addTodo">Add Todo β</button>
</div>
</template>
<script setup lang="ts">
// π― Define types for your data
interface User {
id: number;
name: string;
email: string;
}
interface Todo {
id: number;
text: string;
completed: boolean;
emoji: string; // π¨ Every todo needs an emoji!
}
// π Reactive state with full type safety
const user = ref<User>({
id: 1,
name: "Sarah",
email: "[email protected]"
});
const todos = ref<Todo[]>([
{ id: 1, text: "Learn Nuxt 3", completed: false, emoji: "π" },
{ id: 2, text: "Master TypeScript", completed: true, emoji: "π" }
]);
// β¨ Type-safe methods
const addTodo = () => {
const newTodo: Todo = {
id: Date.now(),
text: "New awesome todo",
completed: false,
emoji: "β"
};
todos.value.push(newTodo);
};
</script>
π‘ Practical Examples
π Example 1: E-commerce Product Page
Letβs build a real product page with TypeScript:
// π pages/products/[id].vue
<template>
<div class="product-page">
<div v-if="pending">Loading product... π</div>
<div v-else-if="error">Error: {{ error.message }} π</div>
<div v-else-if="product" class="product-details">
<h1>{{ product.emoji }} {{ product.name }}</h1>
<p class="price">${{ product.price }}</p>
<p class="description">{{ product.description }}</p>
<button @click="addToCart" :disabled="isAddingToCart">
{{ isAddingToCart ? 'Adding... β³' : 'Add to Cart π' }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
// π― Define product type
interface Product {
id: number;
name: string;
price: number;
description: string;
emoji: string;
category: 'electronics' | 'clothing' | 'books';
inStock: boolean;
}
// π Get route parameter with type safety
const route = useRoute();
const productId = route.params.id as string;
// π Fetch product data with full type safety
const { data: product, pending, error } = await $fetch<Product>(`/api/products/${productId}`);
// π Cart functionality
const isAddingToCart = ref(false);
const addToCart = async () => {
if (!product.value) return;
isAddingToCart.value = true;
try {
await $fetch('/api/cart/add', {
method: 'POST',
body: {
productId: product.value.id,
quantity: 1
}
});
// π Success feedback
await navigateTo('/cart');
} catch (error) {
console.error('Failed to add to cart:', error);
} finally {
isAddingToCart.value = false;
}
};
// π¨ SEO with types
useSeoMeta({
title: product.value ? `${product.value.emoji} ${product.value.name}` : 'Product',
description: product.value?.description || 'Amazing product'
});
</script>
π Example 2: API Route with Type Safety
Letβs create a type-safe API route:
// π server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
// π― Type-safe parameter extraction
const productId = getRouterParam(event, 'id');
if (!productId) {
throw createError({
statusCode: 400,
statusMessage: 'Product ID is required π«'
});
}
// π Validate ID format
const id = parseInt(productId);
if (isNaN(id)) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid product ID format π'
});
}
// ποΈ Mock database query (in real app, use your DB)
const products: Product[] = [
{
id: 1,
name: "TypeScript Handbook",
price: 29.99,
description: "Learn TypeScript like a pro!",
emoji: "π",
category: "books",
inStock: true
},
{
id: 2,
name: "Vue 3 Course",
price: 49.99,
description: "Master Vue 3 with composition API",
emoji: "π",
category: "electronics",
inStock: true
}
];
// π Find product
const product = products.find(p => p.id === id);
if (!product) {
throw createError({
statusCode: 404,
statusMessage: 'Product not found π’'
});
}
// β
Return typed response
return product;
});
// π― Share types between server and client
interface Product {
id: number;
name: string;
price: number;
description: string;
emoji: string;
category: 'electronics' | 'clothing' | 'books';
inStock: boolean;
}
π§ Example 3: Composable with TypeScript
Create reusable logic with full type safety:
// π composables/useShoppingCart.ts
export const useShoppingCart = () => {
// π― Define cart item type
interface CartItem {
id: number;
productId: number;
name: string;
price: number;
quantity: number;
emoji: string;
}
// π Cart state
const cartItems = ref<CartItem[]>([]);
const isLoading = ref(false);
// π° Computed properties with type inference
const totalItems = computed(() =>
cartItems.value.reduce((sum, item) => sum + item.quantity, 0)
);
const totalPrice = computed(() =>
cartItems.value.reduce((sum, item) => sum + (item.price * item.quantity), 0)
);
// β Add item to cart
const addToCart = async (product: Omit<CartItem, 'id' | 'quantity'>) => {
isLoading.value = true;
try {
const existingItem = cartItems.value.find(item => item.productId === product.productId);
if (existingItem) {
existingItem.quantity += 1;
} else {
cartItems.value.push({
...product,
id: Date.now(),
quantity: 1
});
}
// π Show success message
console.log(`Added ${product.emoji} ${product.name} to cart!`);
} catch (error) {
console.error('Failed to add to cart:', error);
} finally {
isLoading.value = false;
}
};
// ποΈ Remove item from cart
const removeFromCart = (itemId: number) => {
const index = cartItems.value.findIndex(item => item.id === itemId);
if (index > -1) {
const item = cartItems.value[index];
console.log(`Removed ${item.emoji} ${item.name} from cart`);
cartItems.value.splice(index, 1);
}
};
// π Clear entire cart
const clearCart = () => {
cartItems.value = [];
console.log('Cart cleared! π§Ή');
};
return {
// State
cartItems: readonly(cartItems),
isLoading: readonly(isLoading),
// Computed
totalItems,
totalPrice,
// Actions
addToCart,
removeFromCart,
clearCart
};
};
π Advanced Concepts
π§ββοΈ Advanced Topic 1: Plugin Development
Create type-safe plugins for Nuxt 3:
// π plugins/api.client.ts
export default defineNuxtPlugin(() => {
// π― Define API client interface
interface ApiClient {
get<T>(url: string): Promise<T>;
post<T>(url: string, data: any): Promise<T>;
put<T>(url: string, data: any): Promise<T>;
delete<T>(url: string): Promise<T>;
}
// π§ Create typed API client
const apiClient: ApiClient = {
async get<T>(url: string): Promise<T> {
return await $fetch<T>(url, { method: 'GET' });
},
async post<T>(url: string, data: any): Promise<T> {
return await $fetch<T>(url, { method: 'POST', body: data });
},
async put<T>(url: string, data: any): Promise<T> {
return await $fetch<T>(url, { method: 'PUT', body: data });
},
async delete<T>(url: string): Promise<T> {
return await $fetch<T>(url, { method: 'DELETE' });
}
};
// π Provide to the app
return {
provide: {
api: apiClient
}
};
});
// π― Use in components
declare module '#app' {
interface NuxtApp {
$api: ApiClient;
}
}
ποΈ Advanced Topic 2: Middleware with Types
Create type-safe middleware:
// π middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
// π― Define user type
interface User {
id: number;
email: string;
role: 'admin' | 'user' | 'guest';
}
// π Check authentication
const user = useCookie<User | null>('user');
if (!user.value) {
// π« Redirect to login
return navigateTo('/login');
}
// π‘οΈ Check role-based access
const protectedRoutes = ['/admin', '/dashboard'];
const adminRoutes = ['/admin'];
if (adminRoutes.some(route => to.path.startsWith(route))) {
if (user.value.role !== 'admin') {
throw createError({
statusCode: 403,
statusMessage: 'Admin access required π'
});
}
}
console.log(`β
Access granted for ${user.value.email}`);
});
β οΈ Common Pitfalls and Solutions
π± Pitfall 1: Auto-import Confusion
// β Wrong way - manual imports when auto-imports exist
import { ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
export default defineComponent({
setup() {
const route = useRoute();
// ...
}
});
// β
Correct way - use auto-imports!
<script setup lang="ts">
// π― These are auto-imported by Nuxt 3
const route = useRoute();
const router = useRouter();
const count = ref(0);
const doubled = computed(() => count.value * 2);
</script>
π€― Pitfall 2: Incorrect API Typing
// β Dangerous - no type safety!
const { data } = await $fetch('/api/users');
data.name; // π₯ Could be undefined!
// β
Safe - with proper typing!
interface User {
id: number;
name: string;
email: string;
}
const { data: user } = await $fetch<User>('/api/users/1');
if (user) {
console.log(user.name); // β
Type-safe!
}
π§ Pitfall 3: Missing Type Definitions
// β Missing proper component props typing
<script setup>
const props = defineProps(['items', 'title']);
</script>
// β
Proper TypeScript component props
<script setup lang="ts">
interface Props {
items: Array<{ id: number; name: string; emoji: string }>;
title: string;
maxItems?: number;
}
const props = withDefaults(defineProps<Props>(), {
maxItems: 10
});
</script>
π οΈ Best Practices
- π― Use Auto-imports: Let Nuxt 3 handle imports automatically
- π Define Clear Interfaces: Create types for all your data structures
- π‘οΈ Validate API Responses: Always type your API calls
- π§ Configure Strict Mode: Enable strict TypeScript checking
- β¨ Use Composables: Extract reusable logic into typed composables
- π¨ Leverage Auto-generated Types: Nuxt 3 generates types for you
- π± Share Types: Use the same types between server and client
π§ͺ Hands-On Exercise
π― Challenge: Build a Blog System
Create a type-safe blog application with Nuxt 3:
π Requirements:
- β Blog posts with title, content, author, and tags
- π·οΈ Categories for organizing posts
- π€ Author profiles with bio and avatar
- π Publication dates and reading time
- π Search functionality
- π¬ Comments system
π Bonus Points:
- Add pagination for blog posts
- Implement tag-based filtering
- Create an admin panel for managing posts
- Add SEO optimization
π‘ Solution
π Click to see solution
// π― types/blog.ts - Shared types
export interface BlogPost {
id: number;
title: string;
content: string;
excerpt: string;
slug: string;
author: Author;
category: Category;
tags: string[];
publishedAt: Date;
readingTime: number;
emoji: string;
featured: boolean;
}
export interface Author {
id: number;
name: string;
email: string;
bio: string;
avatar: string;
socialLinks: {
twitter?: string;
github?: string;
linkedin?: string;
};
}
export interface Category {
id: number;
name: string;
slug: string;
description: string;
emoji: string;
}
// π pages/blog/index.vue
<template>
<div class="blog-index">
<h1>π Our Blog</h1>
<!-- π Search -->
<input
v-model="searchQuery"
type="search"
placeholder="Search posts... π"
class="search-input"
/>
<!-- π·οΈ Category filter -->
<select v-model="selectedCategory" class="category-filter">
<option value="">All Categories</option>
<option v-for="category in categories" :key="category.id" :value="category.slug">
{{ category.emoji }} {{ category.name }}
</option>
</select>
<!-- π Blog posts -->
<div class="posts-grid">
<article
v-for="post in filteredPosts"
:key="post.id"
class="post-card"
>
<h2>
<NuxtLink :to="`/blog/${post.slug}`">
{{ post.emoji }} {{ post.title }}
</NuxtLink>
</h2>
<p class="excerpt">{{ post.excerpt }}</p>
<div class="post-meta">
<span>π€ {{ post.author.name }}</span>
<span>π
{{ formatDate(post.publishedAt) }}</span>
<span>β±οΈ {{ post.readingTime }} min read</span>
</div>
<div class="tags">
<span v-for="tag in post.tags" :key="tag" class="tag">
#{{ tag }}
</span>
</div>
</article>
</div>
</div>
</template>
<script setup lang="ts">
import type { BlogPost, Category } from '~/types/blog';
// π Fetch data with type safety
const { data: posts } = await $fetch<BlogPost[]>('/api/blog/posts');
const { data: categories } = await $fetch<Category[]>('/api/blog/categories');
// π Search and filter state
const searchQuery = ref('');
const selectedCategory = ref('');
// π Computed filtered posts
const filteredPosts = computed(() => {
let filtered = posts.value || [];
// π Filter by search query
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase();
filtered = filtered.filter(post =>
post.title.toLowerCase().includes(query) ||
post.content.toLowerCase().includes(query) ||
post.tags.some(tag => tag.toLowerCase().includes(query))
);
}
// π·οΈ Filter by category
if (selectedCategory.value) {
filtered = filtered.filter(post =>
post.category.slug === selectedCategory.value
);
}
return filtered;
});
// π¨ Utility functions
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(date));
};
// π SEO
useSeoMeta({
title: 'Blog - Amazing Content',
description: 'Discover amazing articles about web development, TypeScript, and more!'
});
</script>
// π server/api/blog/posts.get.ts
export default defineEventHandler(async (event) => {
// ποΈ Mock blog posts (replace with real database)
const posts: BlogPost[] = [
{
id: 1,
title: "Getting Started with Nuxt 3",
content: "Learn how to build amazing applications...",
excerpt: "A comprehensive guide to Nuxt 3 fundamentals",
slug: "getting-started-nuxt-3",
author: {
id: 1,
name: "Sarah Developer",
email: "[email protected]",
bio: "Full-stack developer passionate about TypeScript",
avatar: "/avatars/sarah.jpg",
socialLinks: {
twitter: "@sarahdev",
github: "sarahdev"
}
},
category: {
id: 1,
name: "Tutorials",
slug: "tutorials",
description: "Step-by-step guides",
emoji: "π"
},
tags: ["nuxt", "vue", "typescript"],
publishedAt: new Date('2024-01-15'),
readingTime: 8,
emoji: "π",
featured: true
}
// ... more posts
];
return posts;
});
π Key Takeaways
Youβve learned so much about Nuxt 3 with TypeScript! Hereβs what you can now do:
- β Set up Nuxt 3 projects with zero-config TypeScript πͺ
- β Create type-safe pages and components π‘οΈ
- β Build API routes with full type safety π
- β Use composables for reusable logic π
- β Implement middleware with proper typing π‘οΈ
- β Debug TypeScript issues in Nuxt 3 applications π
- β Apply best practices for production apps π
Remember: Nuxt 3 + TypeScript is an incredibly powerful combination that makes building Vue applications both enjoyable and safe! π€
π€ Next Steps
Congratulations! π Youβve mastered Nuxt 3 with TypeScript!
Hereβs what to do next:
- π» Practice building the blog exercise above
- ποΈ Create a full-stack project with Nuxt 3
- π Explore Nuxt 3 modules and their TypeScript integration
- π Check out our next tutorial on Advanced Vue 3 Composition API with TypeScript!
Remember: Every Nuxt 3 expert started with their first project. Keep building, keep learning, and most importantly, have fun creating amazing applications! π
Happy coding with Nuxt 3 and TypeScript! ππβ¨