+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 185 of 355

πŸ“˜ Nuxt 3 with TypeScript: Vue Framework

Master nuxt 3 with typescript: vue framework in TypeScript with practical examples, best practices, and real-world applications πŸš€

πŸš€Intermediate
25 min read

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:

  1. Zero Configuration πŸ”§: TypeScript works out of the box
  2. Auto-imports ⚑: Composables and utilities with full type support
  3. Full-stack Types 🌐: Shared types between client and server
  4. Developer Experience πŸ’»: Incredible IDE support and autocomplete
  5. 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

  1. 🎯 Use Auto-imports: Let Nuxt 3 handle imports automatically
  2. πŸ“ Define Clear Interfaces: Create types for all your data structures
  3. πŸ›‘οΈ Validate API Responses: Always type your API calls
  4. πŸ”§ Configure Strict Mode: Enable strict TypeScript checking
  5. ✨ Use Composables: Extract reusable logic into typed composables
  6. 🎨 Leverage Auto-generated Types: Nuxt 3 generates types for you
  7. πŸ“± 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:

  1. πŸ’» Practice building the blog exercise above
  2. πŸ—οΈ Create a full-stack project with Nuxt 3
  3. πŸ“š Explore Nuxt 3 modules and their TypeScript integration
  4. 🌟 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! πŸŽ‰πŸš€βœ¨