Building Modern Web Applications with Next.js and React

As frontend technology advances rapidly, building efficient, modern web applications is increasingly important. This article presents a complete development approach using Next.js, React, shadcn, TailwindCSS, Zod, Zustand, React Query, and openapi-react-query.
1. Technology Stack Overview
Next.js
A React-based framework supporting server-side rendering (SSR), static site generation (SSG), and full-stack development. It improves SEO capabilities and optimizes performance.
React
A modern, component-based frontend library and a core tool for building user interfaces.
shadcn
A component toolkit combining Radix UI and TailwindCSS, offering a broad selection of foundational UI components.
TailwindCSS
A utility CSS framework for quickly building responsive, modern interfaces through class names.
Zod
A type-safe validation library for validating form data or API request data, with strong TypeScript integration.
Zustand
A lightweight state management tool suited to complex but localized application state.
React Query
A server state management tool that handles data fetching, caching, synchronization, and updates.
openapi-react-query
Generates type-safe React Query API clients from OpenAPI specifications, improving development efficiency and safety.
2. Quick Project Setup
2.1 Create a Next.js Project
Initialize a project with TypeScript:
npx create-next-app@latest my-nextjs-app --typescript
cd my-nextjs-app
2.2 Install Dependencies
Install the core libraries in the stack:
npm install tailwindcss postcss autoprefixer zustand @tanstack/react-query zod openapi-react-query
npm install @shadcn/ui
2.3 Configure TailwindCSS
Initialize the TailwindCSS configuration file:
npx tailwindcss init
Update tailwind.config.js:
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
"./app/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
Import TailwindCSS in styles/globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
2.4 Integrate React Query
Create a React Query client:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export default function App({ Component, pageProps }: any) {
return (
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>
);
}
2.5 Configure openapi-react-query
Generate an API client from the OpenAPI specification:
npx openapi-codegen generate --input ./path-to-openapi.json --output ./generated --client react-query
Provide the API configuration in the project:
import { createClient, ApiProvider } from "./generated";
const apiClient = createClient({
baseUrl: "https://api.example.com",
fetch: async (url, options) => {
// Add authentication here, such as a token in the request headers
const token = "your-auth-token";
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
},
});
export default function App({ Component, pageProps }: any) {
return (
<ApiProvider client={apiClient}>
<Component {...pageProps} />
</ApiProvider>
);
}
3. Form Development with shadcn and Zod
Using a registration form as an example, the following demonstrates how to combine shadcn and Zod to build a type-safe, user-friendly form.
3.1 Define Validation Rules
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
export type UserFormValues = z.infer<typeof userSchema>;
3.2 Build the Form Component
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
export default function UserForm() {
const form = useForm<UserFormValues>({
resolver: zodResolver(userSchema),
defaultValues: {
name: "",
email: "",
password: "",
},
});
const onSubmit = (values: UserFormValues) => {
console.log("Submitted values:", values);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
name="name"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Your name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="email"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="Your email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="password"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" placeholder="Your password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Register</Button>
</form>
</Form>
);
}
4. State Management with Zustand
Manage application state, such as global user data, with Zustand:
import create from "zustand";
interface UserState {
user: { name: string; email: string } | null;
setUser: (user: { name: string; email: string }) => void;
}
export const useUserStore = create<UserState>((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
Use Zustand in a component:
import { useUserStore } from "@/store/user";
export default function Profile() {
const user = useUserStore((state) => state.user);
return (
<div>
{user ? <p>Welcome, {user.name}!</p> : <p>Please log in.</p>}
</div>
);
}
5. Conclusion
- Stack advantages: Next.js provides performance optimizations; React and TailwindCSS speed up development; shadcn supplies efficient UI components; Zod ensures type safety; and Zustand and React Query manage state efficiently.
- Use cases: Modern web applications requiring complex interactions, extensive form validation, and both server-side and client-side functionality.
- Extensibility: Clients generated with openapi-react-query can significantly improve collaboration with backend developers.
- Recommended learning resources:
