Vibe coding with AI course ยท Module 2: Next.js Setup

TypeScript for Next.js - Types and Interfaces

10 min read ยท
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:

FeatureTypeInterface
ExtendsYes (with &)Yes (with extends)
Union typesYesNo
PrimitivesYesNo
Re-openNoYes

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 allow any without explicit specification
  • strictNullChecks: 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 skills

Prompt 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");  // OK

3. '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. 1. What is TypeScript?

  2. 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:

Useful articles