Vibe coding with AI course ยท Module 2: Next.js Setup
TypeScript for Next.js - Types and Interfaces
In this lesson15
Your Next.js project uses TypeScript - it's like JavaScript with superpowers! It adds types that help avoid bugs.
What is TypeScript?
TypeScript is JavaScript with a type system. TypeScript code is compiled to regular JavaScript.
// JavaScript - no types
function greet(name) {
return "Hello " + name;
}
greet(123); // Works, but logical error!// TypeScript - with types
function greet(name: string) {
return "Hello " + name;
}
greet(123); // Error: Argument of type 'number' not assignable to 'string'Why TypeScript?
- Safety - errors caught before runtime
- Intellisense - editor suggestions
- Documentation - types are documentation
- Refactoring - easier code changes
- Default in Next.js - create-next-app creates a TypeScript project unless you choose otherwise
Basic Types in TypeScript
1. Primitive types
// String
const shipName: string = "Black Pearl";
const captain: string = "Jack Sparrow";
// Number
const crewCount: number = 42;
const treasureValue: number = 1000000;
// Boolean
const isSailing: boolean = true;
const hasKraken: boolean = false;
// Undefined and Null
let treasure: number | undefined; // Can be number or undefined
let kraken: null | object = null;2. Arrays
// Array of numbers
const levels: number[] = [1, 2, 3, 4, 5];
// Array of strings
const pirates: string[] = ["Jack", "Anne", "Blackbeard"];
// Alternative syntax
const ships: Array<string> = ["Pearl", "Dutchman"];
// Mixed array (union type)
const mixed: (string | number)[] = ["Jack", 42, "Anne", 7];3. Objects
// Inline type
const pirate: {
name: string;
level: number;
ship: string;
} = {
name: "Jack Sparrow",
level: 10,
ship: "Black Pearl"
};
// Problem: we have to repeat types for every pirate!Interfaces - Defining Structures
Interface is like a blueprint for objects - it defines the shape of data.
Basic interface
interface Pirate {
name: string;
level: number;
ship: string;
isActive: boolean;
}
// Now we can reuse this type multiple times
const jack: Pirate = {
name: "Jack Sparrow",
level: 10,
ship: "Black Pearl",
isActive: true
};
const anne: Pirate = {
name: "Anne Bonny",
level: 8,
ship: "Revenge",
isActive: true
};
// Array of pirates
const crew: Pirate[] = [jack, anne];Optional properties
The ? sign means the property is optional:
interface Ship {
name: string;
captain: string;
crewCount: number;
treasure?: number; // Optional - can be undefined
flag?: string; // Optional
}
const pearl: Ship = {
name: "Black Pearl",
captain: "Jack Sparrow",
crewCount: 42
// treasure and flag are optional - can be omitted
};
const dutchman: Ship = {
name: "Flying Dutchman",
captain: "Davy Jones",
crewCount: 100,
treasure: 1000000 // We have treasure this time
};Interface for functions
interface GreetFunction {
(name: string): string;
}
const greetPirate: GreetFunction = (name) => {
return `Ahoy, ${name}!`;
};
console.log(greetPirate("Jack")); // "Ahoy, Jack!"Type vs Interface
TypeScript has two ways to define types: type and interface.
Type
type Pirate = {
name: string;
level: number;
};
type Ship = {
name: string;
captain: string;
};Interface
interface Pirate {
name: string;
level: number;
}
interface Ship {
name: string;
captain: string;
}Differences:
| Feature | Type | Interface |
|---|---|---|
| Extends | Yes (with &) | Yes (with extends) |
| Union types | Yes | No |
| Primitives | Yes | No |
| Re-open | No | Yes |
When to use:
- Interface - for objects, React component props
- Type - for union types, primitives, complex types
Union Types - The "or" Type
// Type can be string OR number
type ID = string | number;
const userId1: ID = "abc123"; // OK
const userId2: ID = 42; // OK
// Type can be several specific values
type Status = "pending" | "sailing" | "docked" | "sunk";
const shipStatus: Status = "sailing"; // OK
const wrongStatus: Status = "flying"; // Error!Types in React Components
Props interface
// Define props types
interface ButtonProps {
text: string;
onClick: () => void;
variant?: "primary" | "secondary"; // Optional
}
// Component with typed props
function Button({ text, onClick, variant = "primary" }: ButtonProps) {
return (
<button
onClick={onClick}
className={`btn btn-${variant}`}
>
{text}
</button>
);
}
// Usage
<Button text="Ahoy!" onClick={() => console.log("Clicked!")} />Children prop
interface CardProps {
title: string;
children: React.ReactNode; // Type for children
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
// Usage
<Card title="Pirate Card">
<p>This is content inside card</p>
</Card>Event handlers
interface FormProps {
onSubmit: (email: string) => void;
}
function Form({ onSubmit }: FormProps) {
const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get("email") as string;
onSubmit(email);
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" />
<button type="submit">Join crew</button>
</form>
);
}Generics - Universal Types
Generics allow you to create components/functions that work with different types.
// Generic function
function getFirstItem<T>(items: T[]): T | undefined {
return items[0];
}
const firstNumber = getFirstItem([1, 2, 3]); // number | undefined
const firstString = getFirstItem(["a", "b"]); // string | undefined
// Generic interface
interface Response<T> {
data: T;
status: number;
message: string;
}
interface Pirate {
name: string;
level: number;
}
const pirateResponse: Response<Pirate> = {
data: { name: "Jack", level: 10 },
status: 200,
message: "Success"
};
const stringResponse: Response<string> = {
data: "Hello",
status: 200,
message: "OK"
};TypeScript in Next.js - Practical Examples
Page component
// app/pirates/page.tsx
interface Pirate {
id: number;
name: string;
ship: string;
level: number;
}
export default function PiratesPage() {
const pirates: Pirate[] = [
{ id: 1, name: "Jack Sparrow", ship: "Black Pearl", level: 10 },
{ id: 2, name: "Anne Bonny", ship: "Revenge", level: 8 }
];
return (
<div>
<h1>Pirates</h1>
{pirates.map((pirate) => (
<div key={pirate.id}>
<h2>{pirate.name}</h2>
<p>Ship: {pirate.ship}</p>
<p>Level: {pirate.level}</p>
</div>
))}
</div>
);
}API Route handler
// app/api/pirates/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface Pirate {
id: number;
name: string;
level: number;
}
export async function GET(request: NextRequest) {
const pirates: Pirate[] = [
{ id: 1, name: "Jack Sparrow", level: 10 },
{ id: 2, name: "Anne Bonny", level: 8 }
];
return NextResponse.json({ pirates });
}
export async function POST(request: NextRequest) {
const body: Pirate = await request.json();
// Type validation
if (!body.name || typeof body.level !== 'number') {
return NextResponse.json(
{ error: "Invalid data" },
{ status: 400 }
);
}
return NextResponse.json({ success: true, pirate: body });
}Metadata type
// app/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: "Kraken's Call - Join the Adventure",
description: "Epic pirate game built with Next.js",
keywords: ["pirate", "game", "adventure"],
};
export default function HomePage() {
return <div>Welcome aboard!</div>;
}Type Assertions (as)
Sometimes you know more about a type than TypeScript does:
interface Pirate {
name: string;
level: number;
}
// localStorage returns string | null
const savedData = localStorage.getItem("pirate");
// We tell TypeScript: "I know this is a string"
const pirateData = savedData as string;
// Or for JSON
const pirate = JSON.parse(pirateData) as Pirate;
// Type assertion for a DOM element
const element = document.getElementById("root") as HTMLDivElement;Warning: Use as carefully - you can fool TypeScript and get a runtime error!
Utility Types - Built-in Tools
TypeScript has built-in utility types:
interface Pirate {
name: string;
level: number;
ship: string;
treasure: number;
}
// Partial - all properties optional
type PartialPirate = Partial<Pirate>;
// { name?: string; level?: number; ship?: string; treasure?: number; }
// Pick - select only certain properties
type PiratePreview = Pick<Pirate, "name" | "level">;
// { name: string; level: number; }
// Omit - exclude certain properties
type PirateWithoutTreasure = Omit<Pirate, "treasure">;
// { name: string; level: number; ship: string; }
// Required - all properties required
type RequiredPirate = Required<PartialPirate>;
// { name: string; level: number; ship: string; treasure: number; }any vs unknown vs never
// any - disables type checking (avoid!)
let anything: any = "hello";
anything = 42;
anything.nonExistent(); // No error! Danger!
// unknown - safer version of any
let something: unknown = "hello";
// something.toUpperCase(); // Error! We must check the type
if (typeof something === "string") {
something.toUpperCase(); // OK!
}
// never - a type that never occurs
function throwError(message: string): never {
throw new Error(message);
// Function never returns a value - always throws an error
}tsconfig.json - TypeScript Configuration
Your project has a tsconfig.json file with TypeScript configuration:
{
"compilerOptions": {
"target": "ES2017", // JS version to compile to
"lib": ["dom", "dom.iterable", "esnext"], // Available libraries
"jsx": "react-jsx", // How JSX is compiled (set by Next.js 16)
"module": "esnext", // Module system
"moduleResolution": "bundler", // Imports resolved like a bundler does
"strict": true, // Strict type checking
"esModuleInterop": true, // Compatibility with ES modules
"skipLibCheck": true, // Speeds up compilation
"noEmit": true, // TypeScript only checks, Next.js compiles
"paths": { "@/*": ["./*"] } // The @/ alias in imports
}
}Important options:
strict: true- enables all strict checks (RECOMMENDED!)noImplicitAny: true- doesn't allowanywithout explicit specificationstrictNullChecks: true- checks if value can be null/undefined
Using AI for TypeScript
Cursor handles TypeScript excellently! Ask AI:
Prompt 1:
Create an interface for a pirate card with name, level, ship and an array of skillsPrompt 2:
How to type props for a React component that accepts a title, description and onClick function?Prompt 3:
Fix TypeScript errors in this code: [paste code]Debugging TypeScript Errors
Common errors:
1. Property does not exist
const pirate = { name: "Jack" };
console.log(pirate.level); // Property 'level' does not exist
// Fix: add the property to the interface
interface Pirate {
name: string;
level?: number; // Optional
}2. Type 'X' is not assignable to type 'Y'
const level: number = "10"; // Error: string โ number
// Fix: convert the type
const fixedLevel: number = parseInt("10"); // OK3. 'first' is possibly 'undefined' (older TypeScript versions: Object is possibly 'undefined')
const pirates = [{ name: "Jack" }];
const first = pirates.find(p => p.name === "Jack");
console.log(first.name); // first might be undefined!
// Fix: optional chaining
console.log(first?.name); // OK
// Or a check
if (first) {
console.log(first.name); // OK
}Summary
TypeScript - JavaScript with types, more safety
Basic types - string, number, boolean, array
Interface - defining object structure
Type vs Interface - interface for objects, type for union types
Union types - "or" type (string | number)
Generics - universal types (<T>)
Props typing - interfaces for React components
Utility Types - Partial, Pick, Omit, Required
tsconfig.json - TypeScript configuration
AI helps - Cursor supports TypeScript out-of-the-box
Next steps: In the following modules we'll use TypeScript in every component. You'll learn to type:
- Server Components
- API routes
- Database queries
- Forms
- Hooks
See you there!
Code for this lesson: App.tsx
// Kraken's Call - feature cards described with TypeScript types
interface Feature {
title: string;
description: string;
isNew?: boolean; // optional field
}
interface FeatureCardProps {
feature: Feature;
}
export default function App() {
return (
<main style={{ minHeight: "100vh", background: "#0f172a", padding: 32, fontFamily: "system-ui, sans-serif" }}>
<h1 style={{ color: "white", marginTop: 0 }}>Kraken's Call</h1>
<div style={{ display: "grid", gap: 16 }}>
{features.map((feature) => (
<FeatureCard key={feature.title} feature={feature} />
))}
</div>
</main>
);
}
const features: Feature[] = [
{ title: "Fast messages", description: "Send messages faster than a ship under full sails", isNew: true },
{ title: "Treasure maps", description: "Share treasure locations with your crew" },
{ title: "AI Parrots", description: "Intelligent parrots help with communication" },
];
function FeatureCard({ feature }: FeatureCardProps) {
return (
<div style={{ background: "#1f2937", color: "white", padding: 16, borderRadius: 8 }}>
<h3 style={{ margin: "0 0 8px" }}>
{feature.title} {feature.isNew && <small style={{ color: "#fbbf24" }}>new</small>}
</h3>
<p style={{ margin: 0, color: "#9ca3af" }}>{feature.description}</p>
</div>
);
}Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is TypeScript?
2. What is an interface used for in TypeScript?
Hands-on tasks in the game
- Horizontal ordering
Arrange a typed string variable declaration in TypeScript (left to right):
- Click in order
Build an optional 'treasure' field of type number in an interface: