From Setup to Production: Building Scalable Apps with Next.js and TypeScript.
Share this article
Spread the word with your network
Why Next.js and TypeScript?
Web development has evolved significantly over the past few years. In this comprehensive guide, we'll explore how to build modern, scalable web applications using Next.js and TypeScript.
Next.js provides an excellent foundation for React applications with features like Server-Side Rendering (SSR) for better SEO, Static Site Generation (SSG) for optimal performance, API Routes for backend functionality, and Image Optimization out of the box.
TypeScript adds type safety and developer experience improvements that are crucial for large-scale applications.
Getting Started
First, let's create a new Next.js project with TypeScript:
npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app
npm run devThis will set up a project with TypeScript configuration, Tailwind CSS for styling, and ESLint for code quality.
Project Structure
A well-organized project structure is crucial for maintainability:
1app/
2├── components/ # Reusable UI components
3├── hooks/ # Custom React hooks
4├── lib/ # Utility functions and configurations
5├── types/ # TypeScript type definitions
6└── (routes)/ # App Router pagesKey Features to Implement
Key features include the App Router for better performance and layouts, Server Components to reduce bundle size, Middleware for authentication and redirects, and API Routes for full-stack capabilities.
Advanced Patterns
Custom Hooks
Creating reusable logic with custom hooks:
1import { useState, useEffect } from 'react';
2
3export function useLocalStorage<T>(key: string, initialValue: T) {
4 const [storedValue, setStoredValue] = useState<T>(() => {
5 if (typeof window === 'undefined') {
6 return initialValue;
7 }
8
9 try {
10 const item = window.localStorage.getItem(key);
11 return item ? JSON.parse(item) : initialValue;
12 } catch (error) {
13 console.error(`Error reading localStorage key "${key}":`, error);
14 return initialValue;
15 }
16 });
17
18 const setValue = (value: T | ((val: T) => T)) => {
19 try {
20 const valueToStore = value instanceof Function ? value(storedValue) : value;
21 setStoredValue(valueToStore);
22 if (typeof window !== 'undefined') {
23 window.localStorage.setItem(key, JSON.stringify(valueToStore));
24 }
25 } catch (error) {
26 console.error(`Error setting localStorage key "${key}":`, error);
27 }
28 };
29
30 return [storedValue, setValue] as const;
31}Theme Provider
Implementing a dark mode theme provider:
1"use client";
2
3import { createContext, useContext, useEffect, useState } from 'react';
4
5type Theme = 'light' | 'dark' | 'system';
6
7interface ThemeContextProps {
8 theme: Theme;
9 setTheme: (theme: Theme) => void;
10}
11
12const ThemeContext = createContext<ThemeContextProps | undefined>(undefined);
13
14export function ThemeProvider({ children }: { children: React.ReactNode }) {
15 const [theme, setTheme] = useState<Theme>('system');
16
17 useEffect(() => {
18 const savedTheme = localStorage.getItem('theme') as Theme;
19 if (savedTheme) {
20 setTheme(savedTheme);
21 }
22 }, []);
23
24 useEffect(() => {
25 const root = window.document.documentElement;
26 const isDark = theme === 'dark' ||
27 (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
28
29 if (isDark) {
30 root.classList.add('dark');
31 } else {
32 root.classList.remove('dark');
33 }
34
35 localStorage.setItem('theme', theme);
36 }, [theme]);
37
38 return (
39 <ThemeContext.Provider value={{ theme, setTheme }}>
40 {children}
41 </ThemeContext.Provider>
42 );
43}
44
45export function useTheme() {
46 const context = useContext(ThemeContext);
47 if (context === undefined) {
48 throw new Error('useTheme must be used within a ThemeProvider');
49 }
50 return context;
51}Pro Tip: Always handle edge cases like SSR when working with browser APIs like localStorage or window.
Performance Optimization
Image Optimization
Next.js provides excellent image optimization out of the box:
1import Image from 'next/image';
2
3export function HeroImage() {
4 return (
5 <Image
6 src="/hero-image.jpg"
7 alt="Hero section image"
8 width={1200}
9 height={600}
10 priority
11 className="rounded-lg"
12 />
13 );
14}Code Splitting
Implement dynamic imports for better performance:
1import { lazy, Suspense } from 'react';
2
3const DynamicChart = lazy(() => import('./Chart'));
4
5export function Dashboard() {
6 return (
7 <div>
8 <h1>Dashboard</h1>
9 <Suspense fallback={<div>Loading chart...</div>}>
10 <DynamicChart />
11 </Suspense>
12 </div>
13 );
14}Deployment Considerations
When deploying your Next.js application: Use .env.local for development environment variables, enable compression and minification for build optimization, use Vercel or similar platforms for CDN integration, and consider serverless databases like PlanetScale for database setup.
For more advanced topics, check out the official Next.js documentation.
Happy coding! 🚀

