5 Cursor AI Rules That Save Hours Every Week (Real Examples)
I've been shipping SaaS products with Cursor for over a year. Most of that time, I was wasting hours on repetitive corrections — fixing the same patterns, re-explaining the same conventions, catching the same bugs.
Then I started writing .cursorrules that actually work.
Not generic advice like "write clean code." Real, specific rules that changed how Cursor generates code for my projects. Here are the 5 that save me the most time every single week.
Rule 1: Kill TODOs Before They Become Bugs
This is the single highest-ROI rule I've ever written:
You are an expert software engineer. Always write production-ready code.
Never write placeholder comments like "// TODO" or "// implement later"
— implement it now or tell me what information you need.
Without this rule, Cursor does this constantly:
export async function processPayment(amount: number) {
// TODO: Implement payment processing
console.log('Payment amount:', amount);
}
That TODO becomes a bug in production. Every single time.
With this rule, Cursor generates complete implementations:
export async function processPayment(amount: number) {
if (!process.env.STRIPE_SECRET_KEY) {
throw new Error('STRIPE_SECRET_KEY not configured');
}
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
return await stripe.paymentIntents.create({
amount,
currency: 'usd'
});
}
Time saved: ~2 hours/week chasing down incomplete implementations.
Rule 2: Force Typed Error Handling on Every Async Function
This rule prevents the most common production bug: "it just stopped working and I don't know why."
Always wrap async operations in try/catch. Return typed error
objects, don't just throw strings. Log errors with context:
console.error('[ComponentName] Error:', error).
In UI components, always provide user-facing error states.
Without this rule:
export async function createSubscription(userId: string, planId: string) {
const stripe = new Stripe(process.env.STRIPE_KEY!);
const subscription = await stripe.subscriptions.create({
customer: userId,
items: [{ price: planId }]
});
return subscription;
}
One network blip and this crashes with no useful information.
With this rule:
export async function createSubscription(userId: string, planId: string) {
try {
const stripe = new Stripe(process.env.STRIPE_KEY!);
const subscription = await stripe.subscriptions.create({
customer: userId,
items: [{ price: planId }]
});
return { success: true, data: subscription };
} catch (error) {
console.error('[createSubscription] Failed:', error);
return {
success: false,
error: error instanceof Stripe.errors.StripeError
? error.message
: 'Subscription creation failed'
};
}
}
Time saved: ~3 hours/week debugging production errors that would've been caught.
Rule 3: Validate Environment Variables on Startup
I've seen apps start fine, serve 100 requests, then crash because one env var was missing. This rule eliminates that entire class of bugs:
All environment variables must be validated on startup using
a Zod schema in lib/env.ts. Server-only vars have no prefix.
Public vars use NEXT_PUBLIC_ prefix. Never hardcode values
that belong in env.
Without this rule:
// Hardcoded — security risk
const stripe = new Stripe('sk_test_...');
// No validation — crashes at runtime
const apiKey = process.env.API_KEY;
fetch(`https://api.example.com?key=${apiKey}`); // undefined if missing
With this rule:
// lib/env.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
NEXT_PUBLIC_APP_URL: z.string().url(),
});
export const env = envSchema.parse(process.env);
// Usage — fails fast on deploy, not on first request
import { env } from '@/lib/env';
const stripe = new Stripe(env.STRIPE_SECRET_KEY);
Time saved: ~1 hour/week of "why is this undefined in production" debugging.
Rule 4: Stop Dependency Bloat With the 20-Line Rule
Every npm package is a supply chain risk, a bundle size increase, and a maintenance burden. This rule has prevented me from adding 50+ unnecessary packages:
Before suggesting a new npm package, tell me: (1) what it does,
(2) its weekly downloads and last update, (3) whether we could
do it without adding a dependency. Never add dependencies for
tasks under 20 lines of code.
Without this rule:
import { capitalize } from 'lodash'; // 4MB bundle increase
import { formatDate } from 'date-fns'; // 100KB
export function formatUserName(name: string, date: Date) {
return `${capitalize(name)} - ${formatDate(date, 'yyyy-MM-dd')}`;
}
With this rule:
export function formatUserName(name: string, date: Date) {
const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
const formatted = date.toISOString().split('T')[0];
return `${capitalized} - ${formatted}`;
}
Same result. Zero dependencies. 4 lines of code.
Time saved: ~1 hour/week of dependency management, version conflicts, and bundle size debugging.
Rule 5: Server Components by Default (Next.js)
If you're using Next.js 14+, this rule alone cuts your bundle size and eliminates waterfall requests:
This project uses Next.js 14+ with App Router. Always use server
components by default. Add "use client" only when necessary
(event handlers, hooks, browser APIs). Explain your choice
when adding "use client".
Without this rule:
"use client";
import { useState, useEffect } from 'react';
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser);
}, [userId]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
Client component, useEffect fetch, loading state, waterfall request. All unnecessary.
With this rule:
// Server component — no "use client" needed
export async function UserProfile({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId } });
return user ? <div>{user.name}</div> : <div>User not found</div>;
}
Smaller bundle. Faster load. No loading spinner. No waterfall.
Time saved: ~2 hours/week refactoring unnecessary client components back to server components.
The Compound Effect
Each rule saves 1-3 hours per week. Together, that's ~9 hours/week of work that just... doesn't need to happen anymore.
The key insight: good .cursorrules aren't about making Cursor smarter. They're about making it consistent. You define the pattern once, Cursor applies it everywhere, and you stop repeating yourself.
These 5 rules are from my Cursor Rules Pack v2 — a collection of 50 production-tested rules with real TypeScript/Next.js code examples. If you're using Claude Code instead of Cursor, check out the CLAUDE.md Rules Pack — same patterns, optimized for Claude's instruction format.
One good rule prevents one production bug. One production bug costs hours. Do the math.
Top comments (0)