Expert guidance for building modern Next.js applications with TypeScript, React, Shadcn UI, and Tailwind CSS following industry best practices for code structure, performance, and maintainability.
Expert guidance for building modern, performant Next.js applications with TypeScript, React Server Components, Shadcn UI, and Tailwind CSS.
Follow these principles when writing code:
1. **Write concise, technical TypeScript code** with accurate examples
2. **Use functional and declarative programming patterns** - avoid classes
3. **Prefer iteration and modularization** over code duplication
4. **Use descriptive variable names** with auxiliary verbs (e.g., `isLoading`, `hasError`)
5. **Structure files consistently**: exported component, subcomponents, helpers, static content, types
Apply these naming rules throughout the codebase:
Enforce type safety with these guidelines:
1. **Use TypeScript for all code** - prefer interfaces over types
2. **Avoid enums** - use maps instead
3. **Use functional components** with TypeScript interfaces
4. **Always define types** for variables, parameters, and return values
5. **Explicitly declare types** - avoid implicit `any`, prefer `unknown` for edge cases
6. **Colocate types** with relevant files or move shared types to `src/app/types/`
Write clean, readable code:
Build responsive, accessible interfaces:
1. **Use Shadcn UI, Radix, and Tailwind** for components and styling
2. **Implement responsive design** with Tailwind CSS using a mobile-first approach
3. **Follow Tailwind conventions** for consistent spacing, colors, and breakpoints
Maximize performance with these strategies:
1. **Minimize `use client`, `useEffect`, and `setState`** - favor React Server Components (RSC)
2. **Wrap client components in Suspense** with fallback
3. **Use dynamic loading** (`next/dynamic`) for non-critical components
4. **Use `useMemo`, `React.memo`, and `useCallback`** to prevent unnecessary re-renders
5. **Optimize Web Vitals** (LCP, CLS, FID)
Limit `use client` usage:
Build maintainable components:
1. **Single responsibility** - each component should have one clear purpose
2. **Organize with structure** - use subfolders for components, hooks, and types
3. **Split large components** into smaller, reusable ones
4. **Use error boundaries** for larger components
5. **Define props as types** and pass them to components
6. **Extract logic into custom hooks** to keep components focused on presentation
7. **Optimize with `useMemo` and `useCallback`** to prevent unnecessary computations
Implement efficient data fetching:
1. **Use React Query** for data fetching (caching, retries, background refetching)
2. **Implement proper error handling** using React Query's error states
3. **Create reusable hooks** for common data-fetching scenarios
4. **Handle loading and error states** gracefully in the UI
Organize code for maintainability:
1. **Place components, functions, and types close to where they are used**
2. **Each page should have its own folder** with related components and functions
3. **Place shared code at the nearest common ancestor** in the directory hierarchy
Write clean, maintainable functions:
1. **Name functions clearly** to indicate their purpose
2. **Single responsibility** - functions should do only one thing
3. **Proper placement** - follow File Colocation rules
4. **Error handling and logging** - improve debugging and monitoring
5. **Descriptive parameter names** - group into objects or types when necessary
Implement consistent logging:
1. **Use the logging abstraction** at `src/app/functions/log.ts`
2. **Log important events** (function starts, errors, user interactions)
3. **Provide context** using `log.context` for debugging
Example:
```typescript
import { log } from "@/app/functions/log";
log.info("Something happened", {
userId: 123,
userName: "John Doe",
});
log.error("Internal Server Error while switching institution", error);
```
Handle data mutations securely:
1. **Create UI components** that trigger mutations and display data
2. **Use optimistic updates** via hooks for immediate UI feedback
3. **Handle validation and database updates on the server** using server actions
Example server action:
```typescript
"use server";
import { z } from "zod";
import { prisma } from "@/src/app/misc/singletons/prisma";
import { log } from "@/src/app/functions/log";
import { hasPermission } from "@/src/app/functions/server/roles/permission";
const updateCourseSchema = z.object({
courseId: z.string().min(1, "Course ID is required"),
name: z
.string()
.min(1, "Name cannot be empty")
.max(100, "Name must be 100 characters or less"),
});
export async function updateCourseName(courseId: string, name: string) {
try {
log.info("Attempting to update course name", { courseId, name });
const validatedData = updateCourseSchema.parse({ courseId, name });
await hasPermission("update:course-name", validatedData.courseId);
const updatedCourse = await prisma.course.update({
where: { id: validatedData.courseId },
data: { name: validatedData.name },
});
log.info("Course name updated successfully", { courseId });
return updatedCourse;
} catch (error) {
log.error("Failed to update course name", error);
throw new Error("Failed to update course");
}
}
```
Choose the right state management approach:
1. **URL/Query state**: Use `useQueryState` (or `nuqs`) for filters, pagination
2. **Section-specific state**: Use Context + Zustand for forms or dashboards
3. **Global state**: Use Global Zustand for app-wide state (e.g., user authentication)
Properly separate code execution environments:
Follow these important conventions:
1. **Use `nuqs`** for URL search parameter state management
2. **Optimize Web Vitals** (LCP, CLS, FID)
3. **Follow Next.js documentation** for Data Fetching, Rendering, and Routing
4. **Validate inputs** using Zod schemas
5. **Handle errors gracefully** with proper logging and user feedback
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/nextjs-typescript-best-practices/raw