A comprehensive development guide for modern web applications using Next.js, React, Redux, TypeScript with best practices, conventions, and standards for scalable development
This agents.md example showcases industry best practices for AI agent instruction. The agents.md example provides a comprehensive template that you can adapt for your specific project requirements.
Every element in this agents.md example has been carefully designed to optimize OpenAI Codex performance and ensure consistent AI agent behavior across your development workflow.
To use this agents.md example in your project, download the template and customize it according to your specific needs. This agents.md example serves as a solid foundation for your AI agent configuration.
Study the structure and conventions used in this agents.md example to understand how successful projects implement AI agent instruction for optimal results.
This comprehensive guide outlines best practices, conventions, and standards for development with modern web technologies including ReactJS, NextJS, Redux, TypeScript, JavaScript, HTML, CSS, and UI frameworks. The guide emphasizes clean, maintainable, and scalable code following SOLID principles and functional programming patterns.
nextjs-project/
├── public/ # Static assets
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components/ # Reusable components
│ │ ├── ui/ # Shadcn UI components
│ │ └── common/ # Common components
│ ├── lib/ # Utility libraries
│ │ ├── store/ # Redux store configuration
│ │ ├── utils.ts # Utility functions
│ │ └── validations.ts # Zod schemas
│ ├── hooks/ # Custom React hooks
│ ├── types/ # TypeScript type definitions
│ ├── styles/ # Global styles
│ └── constants/ # Application constants
├── tests/ # Test files
├── docs/ # Documentation
├── .env.example # Environment variables template
├── next.config.js # Next.js configuration
├── tailwind.config.js # Tailwind CSS configuration
├── tsconfig.json # TypeScript configuration
└── package.json
handleClick
, handleSubmit
isLoading
, hasError
, canSubmit
useAuth
, useForm
# 1. Create Next.js project with TypeScript
npx create-next-app@latest my-nextjs-app --typescript --tailwind --eslint --app
# 2. Navigate to project directory
cd my-nextjs-app
# 3. Install additional dependencies
npm install @reduxjs/toolkit react-redux
npm install @hookform/resolvers react-hook-form zod
npm install @radix-ui/react-* # Install specific Radix components
npm install dompurify next-i18next
npm install -D @types/dompurify
# 4. Install Shadcn UI
npx shadcn-ui@latest init
# 5. Start development server
npm run dev
# .env.local
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:3001/api
DATABASE_URL=postgresql://username:password@localhost:5432/database
NEXTAUTH_SECRET=your-secret-key
NEXTAUTH_URL=http://localhost:3000
// Example: User Profile Component
interface UserProfileProps {
userId: string;
onUpdate?: (user: User) => void;
}
export function UserProfile({ userId, onUpdate }: UserProfileProps) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
const fetchUser = async () => {
try {
setIsLoading(true);
const userData = await getUserById(userId);
setUser(userData);
} catch (error) {
setHasError(true);
console.error("Failed to fetch user:", error);
} finally {
setIsLoading(false);
}
};
fetchUser();
}, [userId]);
if (isLoading) return <div>Loading...</div>;
if (hasError) return <div>Error loading user</div>;
if (!user) return <div>User not found</div>;
return (
<div className="user-profile">
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
import { memo, useMemo, useCallback } from "react";
interface UserListProps {
users: User[];
onUserSelect: (userId: string) => void;
}
export const UserList = memo(({ users, onUserSelect }: UserListProps) => {
const sortedUsers = useMemo(() => {
return users.sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
const handleUserClick = useCallback(
(userId: string) => {
onUserSelect(userId);
},
[onUserSelect]
);
return (
<div className="user-list">
{sortedUsers.map((user) => (
<div
key={user.id}
onClick={() => handleUserClick(user.id)}
className="user-item"
>
{user.name}
</div>
))}
</div>
);
});
// Example: Server Component with data fetching
interface PostsPageProps {
searchParams: { page?: string; category?: string };
}
export default async function PostsPage({ searchParams }: PostsPageProps) {
const page = Number(searchParams.page) || 1;
const category = searchParams.category || "all";
const posts = await fetchPosts({ page, category });
return (
<div className="posts-page">
<h1>Posts</h1>
<PostsList posts={posts} />
<Pagination currentPage={page} />
</div>
);
}
// Example: TypeScript interfaces and types
interface User {
id: string;
name: string;
email: string;
role: "admin" | "user" | "moderator";
createdAt: Date;
updatedAt: Date;
}
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
type UserCreateInput = Omit<User, "id" | "createdAt" | "updatedAt">;
type UserUpdateInput = Partial<Pick<User, "name" | "email" | "role">>;
// Type guard example
function isUser(obj: unknown): obj is User {
return (
typeof obj === "object" &&
obj !== null &&
"id" in obj &&
"name" in obj &&
"email" in obj
);
}
// Example: Redux slice with TypeScript
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface UserState {
users: User[];
currentUser: User | null;
isLoading: boolean;
error: string | null;
}
const initialState: UserState = {
users: [],
currentUser: null,
isLoading: false,
error: null,
};
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setLoading: (state, action: PayloadAction<boolean>) => {
state.isLoading = action.payload;
},
setUsers: (state, action: PayloadAction<User[]>) => {
state.users = action.payload;
state.isLoading = false;
state.error = null;
},
setCurrentUser: (state, action: PayloadAction<User>) => {
state.currentUser = action.payload;
},
setError: (state, action: PayloadAction<string>) => {
state.error = action.payload;
state.isLoading = false;
},
clearError: (state) => {
state.error = null;
},
},
});
export const { setLoading, setUsers, setCurrentUser, setError, clearError } =
userSlice.actions;
export default userSlice.reducer;
// Selectors
export const selectUsers = (state: RootState) => state.user.users;
export const selectCurrentUser = (state: RootState) => state.user.currentUser;
export const selectUserLoading = (state: RootState) => state.user.isLoading;
export const selectUserError = (state: RootState) => state.user.error;
// Example: Styled component with Tailwind and dark mode
interface CardProps {
title: string;
children: React.ReactNode;
variant?: "default" | "outlined" | "filled";
}
export function Card({ title, children, variant = "default" }: CardProps) {
const baseClasses = "rounded-lg p-6 transition-colors duration-200";
const variantClasses = {
default: "bg-white dark:bg-gray-800 shadow-md",
outlined: "border border-gray-200 dark:border-gray-700",
filled: "bg-gray-50 dark:bg-gray-900",
};
return (
<div className={`${baseClasses} ${variantClasses[variant]}`}>
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
{title}
</h3>
<div className="text-gray-700 dark:text-gray-300">{children}</div>
</div>
);
}
// Example: Component testing with React Testing Library
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { Provider } from "react-redux";
import { store } from "../lib/store";
import { UserProfile } from "../components/UserProfile";
const renderWithProvider = (component: React.ReactElement) => {
return render(<Provider store={store}>{component}</Provider>);
};
describe("UserProfile Component", () => {
test("displays user information correctly", async () => {
const mockUser = {
id: "1",
name: "John Doe",
email: "[email protected]",
};
renderWithProvider(<UserProfile userId="1" />);
await waitFor(() => {
expect(screen.getByText("John Doe")).toBeInTheDocument();
expect(screen.getByText("[email protected]")).toBeInTheDocument();
});
});
test("handles loading state", () => {
renderWithProvider(<UserProfile userId="1" />);
expect(screen.getByText("Loading...")).toBeInTheDocument();
});
});
// Example: Form validation with Zod and React Hook Form
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
age: z.number().min(18, "Must be at least 18 years old"),
});
type UserFormData = z.infer<typeof userSchema>;
export function UserForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<UserFormData>({
resolver: zodResolver(userSchema),
});
const onSubmit = async (data: UserFormData) => {
try {
await createUser(data);
// Handle success
} catch (error) {
// Handle error
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<input
{...register("name")}
placeholder="Name"
className="w-full p-2 border rounded"
/>
{errors.name && (
<p className="text-red-500 text-sm">{errors.name.message}</p>
)}
</div>
<div>
<input
{...register("email")}
type="email"
placeholder="Email"
className="w-full p-2 border rounded"
/>
{errors.email && (
<p className="text-red-500 text-sm">{errors.email.message}</p>
)}
</div>
<button
type="submit"
disabled={isSubmitting}
className="w-full p-2 bg-blue-500 text-white rounded disabled:opacity-50"
>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
</form>
);
}
// Example: Error Boundary component
import { Component, ErrorInfo, ReactNode } from "react";
interface Props {
children: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
};
public static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Uncaught error:", error, errorInfo);
// Log to external service like Sentry
}
public render() {
if (this.state.hasError) {
return (
<div className="error-boundary p-8 text-center">
<h2 className="text-xl font-bold text-red-600 mb-4">
Something went wrong
</h2>
<p className="text-gray-600 mb-4">
We're sorry, but something unexpected happened.
</p>
<button
onClick={() => this.setState({ hasError: false })}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
// Example: Code splitting and lazy loading
import { lazy, Suspense } from "react";
const LazyDashboard = lazy(() => import("./Dashboard"));
const LazySettings = lazy(() => import("./Settings"));
export function App() {
return (
<div className="app">
<Suspense fallback={<div>Loading Dashboard...</div>}>
<LazyDashboard />
</Suspense>
<Suspense fallback={<div>Loading Settings...</div>}>
<LazySettings />
</Suspense>
</div>
);
}
// Example: Input sanitization with DOMPurify
import DOMPurify from "dompurify";
interface SafeHtmlProps {
html: string;
className?: string;
}
export function SafeHtml({ html, className }: SafeHtmlProps) {
const sanitizedHtml = DOMPurify.sanitize(html);
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
/>
);
}
// Example: Accessible form component
export function AccessibleForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
return (
<form role="form" aria-labelledby="form-title">
<h2 id="form-title">User Registration</h2>
<div className="form-group">
<label htmlFor="email" className="required">
Email Address
</label>
<input
id="email"
type="email"
required
aria-describedby={errors.email ? "email-error" : undefined}
aria-invalid={!!errors.email}
className="form-input"
/>
{errors.email && (
<div id="email-error" role="alert" className="error-message">
{errors.email}
</div>
)}
</div>
<button type="submit" className="submit-button">
Register
<span className="sr-only">(opens in new window)</span>
</button>
</form>
);
}
// Example: i18n configuration
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: "en",
locales: ["en", "es", "fr", "de"],
},
reloadOnPrerender: process.env.NODE_ENV === "development",
};
// Example: Using translations in components
import { useTranslation } from "next-i18next";
import { GetStaticProps } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
export function WelcomePage() {
const { t } = useTranslation("common");
return (
<div>
<h1>{t("welcome.title")}</h1>
<p>{t("welcome.description")}</p>
</div>
);
}
export const getStaticProps: GetStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale ?? "en", ["common"])),
},
});
# Build for production
npm run build
# Start production server
npm start
# Export static files (if using static export)
npm run export
# Production environment variables
NODE_ENV=production
NEXT_PUBLIC_APP_URL=https://yourdomain.com
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
DATABASE_URL=postgresql://user:password@host:port/database
REDIS_URL=redis://host:port
NEXTAUTH_SECRET=your-production-secret
NEXTAUTH_URL=https://yourdomain.com
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
images: {
domains: ["example.com", "cdn.example.com"],
formats: ["image/webp", "image/avif"],
},
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
],
},
];
},
};
module.exports = nextConfig;
// Example: Error tracking setup
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
});
// Custom error logging utility
export const logger = {
error: (message: string, error?: Error, context?: Record<string, any>) => {
console.error(message, error, context);
Sentry.captureException(error || new Error(message), {
extra: context,
});
},
warn: (message: string, context?: Record<string, any>) => {
console.warn(message, context);
},
info: (message: string, context?: Record<string, any>) => {
console.info(message, context);
},
};
Solution:
useEffect
for client-only codedynamic
imports with ssr: false
for client-only componentsSolution:
React.memo
and useMemo
Solution:
Solution:
Metadata
API in App RouterNote: This guide is based on the latest stable versions of Next.js, React, and TypeScript. Please adjust configurations and examples according to your specific project requirements and the versions you are using. Always refer to the official documentation for the most up-to-date information.