A comprehensive guide to setting up a modern Next.js project with TypeScript, Tailwind CSS, and best practices.
Aram Tutorials
Welcome to this comprehensive tutorial on setting up a modern Next.js project with TypeScript. We'll cover everything from initial setup to deployment best practices.
Understanding of TypeScript basics (recommended)
In this tutorial, we'll create a modern Next.js application with the following features:
Start by creating a new Next.js project with TypeScript template:
npx create-next-app@latest my-nextjs-app --typescript --tailwind --eslint --app
cd my-nextjs-app
This command creates a new Next.js project with:
Let's examine the generated project structure:
my-nextjs-app/
āāā src/
ā āāā app/
ā āāā globals.css
ā āāā layout.tsx
ā āāā page.tsx
āāā public/
āāā next.config.js
āāā package.json
āāā tailwind.config.js
āāā tsconfig.json
The src/app directory is where you'll build your application. The App Router uses file-based routing, where each folder represents a route segment.
Next.js automatically configures TypeScript, but let's understand the key configuration files:
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Let's create a reusable Button component with proper TypeScript types:
import React from 'react';
import { cn } from '@/lib/utils';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
export function Button({
variant = 'primary',
size = 'md',
className,
children,
...props
}: ButtonProps) {
return (
<button
className={cn(
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50',
{
'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
'bg-gray-200 text-gray-900 hover:bg-gray-300': variant === 'secondary',
'border border-gray-300 bg-transparent hover:bg-gray-50': variant === 'outline',
'h-8 px-3 text-sm': size === 'sm',
'h-10 px-4 text-base': size === 'md',
'h-12 px-6 text-lg': size === 'lg',
},
className
)}
{...props}
>
{children}
</button>
);
}
Always use TypeScript's strict mode to catch potential issues early. This includes proper type annotations for props, state, and function parameters.
Here are some essential best practices when working with Next.js and TypeScript:
Always define interfaces for your component props:
interface UserCardProps {
user: {
id: string;
name: string;
email: string;
};
onEdit?: (id: string) => void;
}
Type your API responses for better development experience:
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
Always use Next.js Image component instead of regular img tags for
automatic optimization, lazy loading, and better performance.
import Image from 'next/image';
export function UserAvatar({ src, alt }: { src: string; alt: string }) {
return (
<Image
src={src}
alt={alt}
width={64}
height={64}
className="rounded-full"
priority={false} // Set to true for above-the-fold images
/>
);
}
Hydration Mismatch: Make sure your server-side rendered content matches what's rendered on the client. Avoid using browser-only APIs during initial render.
Here are some common issues you might encounter:
dynamic imports for client-only componentsNEXT_PUBLIC_import dynamic from 'next/dynamic';
// This component will only render on the client
const ClientOnlyComponent = dynamic(
() => import('@/components/ClientOnlyComponent'),
{ ssr: false }
);
npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom
Create a jest.config.js file in your project root:
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);
Next.js applications are ready for production out of the box. The framework automatically optimizes your code for the best performance.
The easiest way to deploy your Next.js app is using Vercel:
npm install -g vercel
vercel --prod
npm run build
npm start
Congratulations! You now have a solid foundation for building modern web applications with Next.js and TypeScript. Here are some next steps to consider:
Official Next.js documentation with comprehensive guides and API reference
Complete guide to TypeScript features and best practices
Utility-first CSS framework documentation
Collection of example projects showcasing different Next.js features
That's it! You're now ready to build amazing web applications with Next.js and TypeScript. Remember to keep practicing and exploring the ecosystem to become more proficient.
If you found this tutorial helpful, consider supporting my work to help me create more quality content.