Production-ready rules for building full-stack SSR applications with Next.js, React, Redux Toolkit, Node.js/Express and TypeScript.
Ever find yourself losing hours jumping between React client bugs, Express server issues, and Redux state management problems? Your development velocity doesn't have to suffer from constant context switching between frontend and backend concerns.
Building modern web applications with React, Redux, Node.js, and Express creates a perfect storm of productivity killers:
These aren't just minor inconveniences—they're velocity killers that compound into weeks of debugging time.
These Cursor Rules transform your development environment into a full-stack powerhouse that enforces production patterns from day one. Instead of learning these patterns through painful debugging sessions, you get them built into every file you create.
What You Get:
Instead of spending days configuring server-side rendering:
// Auto-generated with proper patterns
export default async function UserProfile({ params }: { params: { id: string } }) {
const user = await api.users.getById(params.id); // Server-side fetch
return <UserView user={user} />; // Hydrated on client
}
Feature-complete slices generated with proper async handling:
// Generated with proper error states and loading patterns
export const fetchUser = createAsyncThunk('users/fetch', async (id: string) => {
const response = await api.get(`/users/${id}`);
return response.data;
});
Express routes with automatic TypeScript validation:
// Request/response types enforced automatically
export const getUserHandler = asyncHandler(async (req: TypedRequest<GetUserParams>, res) => {
const user = await userService.findById(req.params.id);
res.json({ user }); // Fully typed response
});
Before: Write Express route → manually create types → test with Postman → fix type mismatches → update Redux → handle edge cases
After: Define schema → auto-generate Express route with validation → RTK Query hooks created automatically → error handling included
// One schema definition generates everything
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1)
});
// Auto-generates: API route, Redux slice, React hooks, TypeScript types
Before: Create component → set up Redux connection → handle loading states → add error boundaries → write tests
After: Server Component by default → client interaction clearly marked → automatic error boundaries → testing patterns included
// Server Component with data fetching
export default async function UserDashboard() {
const users = await getUsers(); // Server-side, no loading state needed
return <UserList users={users} />; // Rendered on server
}
// Client interactivity clearly separated
'use client';
export function UserActions() {
const dispatch = useAppDispatch();
// Client-only logic here
}
Before: Set up test environment → mock Redux store → configure server testing → write integration tests
After: Test patterns included with every component/route → proper mocking setup → coverage requirements enforced
.cursor/rules fileCreate a new feature using the established patterns:
/app/users/
├── page.tsx # Server Component entry point
├── UserList.client.tsx # Client-side interactions
├── userSlice.ts # Redux logic
├── api.ts # API integration
└── tests/ # Co-located tests
Follow the production-ready Express patterns:
// Automatic error handling, validation, and security
app.use('/api/v1', userRouter);
app.use(errorHandler); // Centralized error management
Production configurations included:
The difference isn't just in code quality—it's in shipping velocity. When your development environment enforces production patterns automatically, you spend time building features instead of debugging architectural decisions.
Ready to transform your full-stack development workflow? These rules turn months of painful pattern learning into immediate, production-ready development.
You are an expert in React 18, Redux Toolkit & RTK Query, Next.js 14 (App Router & React Server Components), Node.js 20, Express 4+, TypeScript 5, Jest/RTL, Docker, PM2, Webpack/Babel, ESLint + Prettier.
Key Principles
- Use TypeScript everywhere (`strict: true`) to surface type errors at compile time.
- Keep code functional and declarative; prefer pure functions & hooks over classes.
- One responsibility per module; collocate files by feature rather than by type.
- Default to server-side rendering & React Server Components to minimise client bundle size.
- Configuration, secrets and URLs must come from environment variables – never hard-code.
- Fail fast: validate input early and return explicit, user-centric error objects.
- Prefer convention over configuration—consistent naming, folder layout and patterns accelerate onboarding.
- Treat tests, lint, type-checks and formatting as blocking steps of CI.
Language-Specific Rules (TypeScript)
- Use `interface` for public shapes, `type` for unions/intersections.
- Never use `any`; fall back to `unknown` and refine with guards.
- Enable `--strictNullChecks` and `--noImplicitReturns`.
- Declare React FCs with `const Component = ({…}: Props) => {}`; avoid `React.FC` implicit children—type children explicitly.
- Prefer `export const foo` over default exports to enable safer refactors.
- Use `async/await` only at I/O boundaries; keep internal logic synchronous when possible.
- Destructure props/args immediately; never mutate function arguments.
- Naming conventions:
• Booleans start with `is/has/should`.
• React server components end with `.server.tsx`.
• Client components end with `.client.tsx`.
• Redux slices end with `Slice.ts`.
- File structure example:
/app
/[feature]
index.server.tsx ← RSC entry
View.client.tsx ← interactive UI
slice.ts
api.ts
tests/
Error Handling & Validation
- Express:
• Wrap all route handlers in `asyncHandler` to forward rejections to `next(err)`.
• Centralised `error.middleware.ts` maps internal errors to JSON `{status, code, message}`.
- Node:
• Register `process.on('unhandledRejection')` & `process.on('uncaughtException')` to log and gracefully shut down (PM2 will restart).
- Validation:
• Validate request body/query/params with Zod/Joi in dedicated middleware.
• On the client, validate form state before calling APIs; show field-level messages.
- Redux:
• Use `createAsyncThunk` or RTK Query; handle `rejected` cases by dispatching `addToast(error.message)`.
- Use the correct HTTP status codes: 4xx for client issues, 5xx for server faults.
Framework-Specific Rules
React / Next.js
- Place new pages in `/app/(site)/page.tsx` using the App Router.
- Use Server Components by default; mark interactive pieces with `"use client"`.
- Fetch data in Server Components with `await fetch` or RTK Query prefetch.
- Encapsulate UI logic in custom hooks `useX`; do not call hooks conditionally.
- Use `next/image` for responsive images and `next/font` for self-hosted fonts.
- Handle errors with Next.js `error.tsx` boundaries.
Redux Toolkit
- Group slices by feature; collocate selectors & actions in the same file.
- Immer is built-in; mutate the draft state directly for clarity.
- Use `extraReducers` for async logic via `createAsyncThunk` or RTK Query.
- Do not store derived data in Redux; compute with `createSelector`.
- Prefer RTK Query for API calls; server-side prefetch with `store.dispatch(api.endpoints.X.initiate())`.
Node.js / Express
- Directory layout:
/src
/api/v1
users.router.ts
users.controller.ts
users.schema.ts
/middleware
/utils
server.ts
- RESTful endpoints: GET/POST/PUT/DELETE with clear nouns.
- Accept JSON only; use `express.json({limit:'1mb'})`.
- Enable `compression`, `helmet`, and CORS middleware.
- Mount versioned API under `/api/v1`.
- `NODE_ENV=production`, set `trust proxy`, enable `etag` for caching.
Additional Sections
Testing
- Jest with `--runInBand` for deterministic SSR tests.
- React: React Testing Library; test user flows, not implementation details.
- Express: Supertest for HTTP layer; stub external services with MSW.
- Minimum 80 % coverage; all reducers and critical utils must have tests.
Performance
- Enable Next.js `experimental.serverActions` to reduce client calls.
- Set `Cache-Control` headers in Express (`immutable, max-age=31536000`) for static assets.
- Use Redis or in-memory LRU cache for frequently hit endpoints.
- Run `next build --profile` each release; keep client JS < 200 kB.
Security
- Sanitize inputs with `express-validator`.
- Rate-limit auth routes (e.g., 5 req/min/IP).
- Use `helmet()` and a strict Content-Security-Policy.
- Store secrets in Docker secrets or AWS SSM; never commit `.env`.
Tooling & Dev-Ops
- ESLint (airbnb + typescript) and Prettier; enforce via Husky pre-commit.
- Docker multi-stage builds:
FROM node:20-alpine AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM node:20-alpine
COPY --from=build /app .
CMD ["pm2-runtime","ecosystem.config.js"]
- Use PM2 `--no-autorestart` inside the container; rely on container health-check to restart.
Example: Express Error Middleware
```ts
// middleware/error.middleware.ts
export function errorHandler(
err: unknown,
_req: Request,
res: Response,
_next: NextFunction
) {
const status = err instanceof HttpError ? err.status : 500;
res.status(status).json({
status,
message: err instanceof Error ? err.message : 'Unknown error',
});
}
```
Example: Redux Slice
```ts
// features/auth/authSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import api from './api';
export const login = createAsyncThunk('auth/login', api.login);
interface AuthState {
token: string | null;
status: 'idle' | 'loading' | 'failed';
}
const initialState: AuthState = { token: null, status: 'idle' };
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout: (state) => {
state.token = null;
},
},
extraReducers: (builder) =>
builder
.addCase(login.pending, (state) => {
state.status = 'loading';
})
.addCase(login.fulfilled, (state, { payload }) => {
state.status = 'idle';
state.token = payload.token;
})
.addCase(login.rejected, (state) => {
state.status = 'failed';
}),
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
```