Let me be brutally honest.
Most Next.js codebases Iβve reviewed lately are a complete mess when it comes to API handling:
-
useEffecteverywhere - API calls inside components
- No caching
- Broken auth (random logouts π€‘)
- Copy-pasted logic across files
And the worst part?
π These apps workβ¦ until they scale.
β οΈ The Problem Nobody Talks About
Frontend devs focus a lot on UIβ¦
But ignore architecture.
Meanwhile, backend engineers:
- design layers
- enforce contracts
- optimize performance
π Frontend?
"Just fetch it bro" π
π‘ So I Built This Instead
A production-grade API layer using modern tools:
π§± Stack
- Next.js (App Router)
- Axios (client-side)
- TanStack Query (API state)
- Zustand (global state)
- Zod (validation)
- React Hook Form (forms)
- JWT Auth (with refresh token rotation)
π§ Architecture Overview
[ UI Components ]
β
[ Custom Hooks (React Query) ]
β
[ Services Layer ]
β
[ Axios / Fetch Layer ]
β
[ NestJS Backend ]
π Clean separation = scalable system
π₯ Step 1: STOP Calling APIs in Components
β Bad
useEffect(() => {
fetch("/api/users").then(...)
}, [])
β Good (Service Layer)
// services/user.service.ts
export const getUsers = async () => {
const res = await api.get("/users");
return res.data;
};
π Your components should NEVER know how APIs work.
β‘ Step 2: React Query = Game Changer
Using TanStack Query:
const { data, isLoading } = useQuery({
queryKey: ["users"],
queryFn: getUsers,
});
What you get for free:
- π caching
- π retries
- β‘ background refetch
- π pagination support
π This replaces Redux (for API state)
π§© Step 3: Axios with Interceptors (REAL AUTH)
// lib/api.ts
import axios from "axios";
export const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
withCredentials: true,
});
π Token Handling (IMPORTANT)
Flow:
Request β 401 β Refresh Token β Retry Request
Interceptor:
api.interceptors.response.use(
res => res,
async (error) => {
if (error.response.status === 401) {
const newToken = await refreshToken();
error.config.headers.Authorization = `Bearer ${newToken}`;
return api(error.config);
}
return Promise.reject(error);
}
);
π This is what separates amateur apps vs production apps
π§ Step 4: Zustand (Keep It Simple)
Using Zustand:
import { create } from "zustand";
export const useAuthStore = create((set) => ({
user: null,
token: null,
setAuth: (data) => set(data),
logout: () => set({ user: null, token: null }),
}));
β no boilerplate
β super fast
β perfect with React Query
π‘οΈ Step 5: Zod = No More API Surprises
Using Zod:
const userSchema = z.object({
id: z.string(),
name: z.string(),
});
Why this matters:
Without validation:
Backend changes β frontend silently breaks π¬
With Zod:
β strict validation
β type safety
β safer production
π§Ύ Step 6: Forms That Donβt Lag
Using React Hook Form:
const { register, handleSubmit } = useForm();
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} />
</form>
β minimal re-renders
β clean integration with Zod
β better UX
π Step 7: Folder Structure (This is KEY)
/src
/lib
api.ts
fetcher.ts
/services
auth.service.ts
user.service.ts
/hooks
useApiQuery.ts
useApiMutation.ts
/store
auth.store.ts
/schemas
user.schema.ts
π This is what makes your app scalable after 6+ months.
π§ Step 8: Custom Hooks = Clean UI
export const useApiQuery = (key, fn) => {
return useQuery({
queryKey: key,
queryFn: fn,
});
};
Now in UI:
const { data } = useApiQuery(["users"], getUsers);
π Clean AF.
π Diagram: Full Flow
[Component]
β
[useApiQuery]
β
[Service Layer]
β
[Axios Instance]
β
[Interceptor]
β
[NestJS API]
π₯ The Real Difference
Before:
- messy code
- duplicated logic
- no caching
- auth bugs
After:
- scalable architecture
- centralized API logic
- automatic caching
- seamless auth flow
β οΈ Controversial Take
If you're still using
useEffectfor API calls in 2025β¦
You're writing legacy code.
Yeah, I said it.
π§ Final Thoughts
Frontend is no longer βjust UIβ.
Itβs:
- data orchestration
- caching strategy
- auth handling
- performance optimization
π Treat it like backend architecture.
π Want This Setup Instantly?
I created a Cursor AI command that generates this entire architecture automatically.
Drop a comment: "setup"
Iβll share it with you π
π₯ Tags
nextjs #nestjs #reactquery #zustand #webdev #typescript #frontend #fullstack #javascript
π Want the full setup?
Iβve created a Cursor AI command that generates this entire architecture automatically.
Comment βsetupβ and Iβll share it π
Top comments (0)