DEV Community

Olivia Craft
Olivia Craft

Posted on

5 Cursor AI Rules That Save Hours Every Week (Real Examples)

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.
Enter fullscreen mode Exit fullscreen mode

Without this rule, Cursor does this constantly:

export async function processPayment(amount: number) {
  // TODO: Implement payment processing
  console.log('Payment amount:', amount);
}
Enter fullscreen mode Exit fullscreen mode

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'
  });
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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'
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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')}`;
}
Enter fullscreen mode Exit fullscreen mode

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}`;
}
Enter fullscreen mode Exit fullscreen mode

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".
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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)