Building Modern Web Applications with Next.js and TypeScript

Building Modern Web Applications with Next.js and TypeScript

Explore the power of Next.js 15 and TypeScript for creating scalable, performant web applications. Learn best practices and advanced patterns.

Saurabh
October 15, 2024
8 min read

Building Modern Web Applications with Next.js and TypeScript

In today's rapidly evolving web development landscape, choosing the right stack is crucial for building scalable and maintainable applications. Next.js 15 combined with TypeScript provides an excellent foundation for modern web development.

Why Next.js?

Next.js has revolutionized React development by providing:

  • Server-Side Rendering (SSR) for better SEO and performance
  • Static Site Generation (SSG) for lightning-fast sites
  • API Routes for full-stack development
  • Automatic Code Splitting for optimized loading
  • Built-in CSS and Sass Support

TypeScript Benefits

TypeScript adds powerful type safety to JavaScript:

interface User {
  id: string;
  name: string;
  email: string;
  role: "admin" | "user";
}

const createUser = (userData: Omit<User, "id">): User => {
  return {
    id: generateId(),
    ...userData,
  };
};

Best Practices

1. Project Structure

Organize your project with clear separation of concerns:

src/
├── components/
├── pages/
├── utils/
├── types/
└── styles/

2. Type Safety

Always define proper interfaces and use TypeScript's strict mode:

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

3. Performance Optimization

  • Use Next.js Image optimization
  • Implement proper caching strategies
  • Leverage React.memo for component optimization

Advanced Patterns

Custom Hooks

Create reusable logic with custom hooks:

import { useState, useEffect } from 'react';

const useLocalStorage = <T>(key: string, initialValue: T) => {
  const [value, setValue] = useState<T>(initialValue);

  useEffect(() => {
    try {
      const item = localStorage.getItem(key);
      if (item) {
        setValue(JSON.parse(item));
      }
    } catch (error) {
      console.error(`Error reading localStorage key "${key}":`, error);
    }
  }, [key]);

  const setStoredValue = (newValue: T | ((val: T) => T)) => {
    try {
      const valueToStore = newValue instanceof Function ? newValue(value) : newValue;
      setValue(valueToStore);
      localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(`Error setting localStorage key "${key}":`, error);
    }
  };

  return [value, setStoredValue] as const;
};

// Usage example
function UserProfile() {
  const [user, setUser] = useLocalStorage('user', { name: '', email: '' });

  return (
    <form>
      <input
        value={user.name}
        onChange={(e) => setUser(prev => ({ ...prev, name: e.target.value }))}
        placeholder="Name"
      />
      <input
        value={user.email}
        onChange={(e) => setUser(prev => ({ ...prev, email: e.target.value }))}
        placeholder="Email"
      />
    </form>
  );
}

API Routes with Validation

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";

const CreateUserSchema = z.object({
  name: z.string().min(1, "Name is required"),
  email: z.string().email("Invalid email format"),
  age: z.number().min(13, "Must be at least 13 years old"),
});

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const validatedData = CreateUserSchema.parse(body);

    // Create user logic here
    const user = await createUser(validatedData);

    return NextResponse.json(
      {
        success: true,
        user,
      },
      { status: 201 }
    );
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        {
          success: false,
          errors: error.errors,
        },
        { status: 400 }
      );
    }

    return NextResponse.json(
      {
        success: false,
        message: "Internal server error",
      },
      { status: 500 }
    );
  }
}

Conclusion

Next.js and TypeScript provide a robust foundation for building modern web applications. The combination offers excellent developer experience, type safety, and performance optimization out of the box.

Start your next project with this powerful stack and experience the difference in development productivity and application quality.

Written by

Saurabh

Related Articles

Enjoyed this article?

Check out more of my writing on frontend development and web technologies