Tools: The .cursorrules I'd Actually Use for a Next.js E-Commerce Project

Tools: The .cursorrules I'd Actually Use for a Next.js E-Commerce Project

Rule 1: Server Components for Product Pages ## Rule 2: Stripe Stays on the Server ## Rule 3: Prices in Cents, Display with Intl ## Rule 4: Route Groups for Clean Architecture ## Rule 5: Inventory Checks are Server-Side ## Rule 6: SEO Metadata on Every Product Page ## The Point Most .cursorrules files are generic. "Use TypeScript." "Prefer functional components." "Write clean code." That's fine for a tutorial project. But when you're building something real, like an e-commerce app with payments, inventory, and SEO requirements, Cursor still doesn't know about Stripe webhook verification, cart race conditions, or product page metadata. I put together a .cursorrules file specifically for Next.js e-commerce projects. Key rules were tested with Cursor CLI to confirm they actually change the output. The rest are documented best practices that solve real e-commerce problems. The problem: Cursor puts 'use client' on everything. Product pages don't need it. They're read-heavy, SEO-critical, and benefit from server rendering. What Cursor generates with this rule: The main page is an async Server Component that fetches products directly: The only client component is the interactive button: Without this rule, Cursor tends to make the entire page a client component with useEffect for data fetching. With it, you get proper server rendering where it matters and client interactivity only where you need it. The problem: Cursor will put Stripe API calls in client components if you're not specific. That means secret keys in the browser bundle. What Cursor generates with this rule: Webhook handler with proper signature verification: Without this rule, Cursor sometimes creates checkout flows that import Stripe helpers directly in client components. With it, all payment logic stays server-side. The problem: Floating point math and money don't mix. $19.99 + $5.00 might equal $24.989999999999998. Cursor doesn't think about this unless you tell it. Why this matters: This prevents an entire class of rounding bugs. When your cart calculates totals, discounts, and tax, integer math gives you exact results. The Intl.NumberFormat API handles locale-specific formatting (commas, decimal points, currency symbols) so you don't have to. Why: Without this, Cursor dumps everything into a flat route structure. Route groups give you separate layouts (the shop has a product sidebar, checkout has a progress stepper, account has a settings nav) without affecting URLs. Why: The classic "two people buy the last item" problem. Cursor doesn't think about race conditions unless you explicitly tell it to wrap inventory checks and order creation in a transaction. Why: E-commerce lives and dies by SEO. Cursor skips metadata by default. This rule makes it generate proper Open Graph tags, structured data, and server-rendered content on every product page. Generic .cursorrules tell Cursor to "write good code." Domain-specific rules tell Cursor how your specific type of project should work. An e-commerce app has different requirements than a SaaS dashboard or a content site. The rules should reflect that. I'm working on more industry-specific setups like this. If "cursorrules for [your project type]" is something you'd actually use, let me know in the comments what you're building. 📝 Previous articles in this series: 💻 Free collection (33 rules): github.com/cursorrulespacks/cursorrules-collection Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or CODE_BLOCK: When building e-commerce product pages, category pages, or search results: - Default to React Server Components - Only use 'use client' for interactive elements: cart buttons, quantity selectors, wishlist toggles, search filters - Product data fetching happens in Server Components, never in useEffect CODE_BLOCK: When building e-commerce product pages, category pages, or search results: - Default to React Server Components - Only use 'use client' for interactive elements: cart buttons, quantity selectors, wishlist toggles, search filters - Product data fetching happens in Server Components, never in useEffect CODE_BLOCK: When building e-commerce product pages, category pages, or search results: - Default to React Server Components - Only use 'use client' for interactive elements: cart buttons, quantity selectors, wishlist toggles, search filters - Product data fetching happens in Server Components, never in useEffect COMMAND_BLOCK: // app/products/page.tsx — Server Component (no 'use client') export default async function ProductsPage() { const products = await getProducts(); return ( <div className="grid grid-cols-3 gap-4"> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> ); } COMMAND_BLOCK: // app/products/page.tsx — Server Component (no 'use client') export default async function ProductsPage() { const products = await getProducts(); return ( <div className="grid grid-cols-3 gap-4"> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> ); } COMMAND_BLOCK: // app/products/page.tsx — Server Component (no 'use client') export default async function ProductsPage() { const products = await getProducts(); return ( <div className="grid grid-cols-3 gap-4"> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> ); } CODE_BLOCK: // components/AddToCartButton.tsx 'use client'; export function AddToCartButton({ productId }: { productId: string }) { // cart interaction logic here } CODE_BLOCK: // components/AddToCartButton.tsx 'use client'; export function AddToCartButton({ productId }: { productId: string }) { // cart interaction logic here } CODE_BLOCK: // components/AddToCartButton.tsx 'use client'; export function AddToCartButton({ productId }: { productId: string }) { // cart interaction logic here } CODE_BLOCK: For payment processing with Stripe: - Never import stripe in client components for server-side operations - All payment intent creation must happen in Server Actions or API routes - Always verify webhook signatures using stripe.webhooks.constructEvent() - Include idempotency keys in payment creation calls CODE_BLOCK: For payment processing with Stripe: - Never import stripe in client components for server-side operations - All payment intent creation must happen in Server Actions or API routes - Always verify webhook signatures using stripe.webhooks.constructEvent() - Include idempotency keys in payment creation calls CODE_BLOCK: For payment processing with Stripe: - Never import stripe in client components for server-side operations - All payment intent creation must happen in Server Actions or API routes - Always verify webhook signatures using stripe.webhooks.constructEvent() - Include idempotency keys in payment creation calls CODE_BLOCK: // app/api/webhooks/stripe/route.ts import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(request: Request) { const body = await request.text(); const sig = request.headers.get('stripe-signature')!; const event = stripe.webhooks.constructEvent( body, sig, process.env.STRIPE_WEBHOOK_SECRET! ); // Handle events... } CODE_BLOCK: // app/api/webhooks/stripe/route.ts import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(request: Request) { const body = await request.text(); const sig = request.headers.get('stripe-signature')!; const event = stripe.webhooks.constructEvent( body, sig, process.env.STRIPE_WEBHOOK_SECRET! ); // Handle events... } CODE_BLOCK: // app/api/webhooks/stripe/route.ts import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(request: Request) { const body = await request.text(); const sig = request.headers.get('stripe-signature')!; const event = stripe.webhooks.constructEvent( body, sig, process.env.STRIPE_WEBHOOK_SECRET! ); // Handle events... } CODE_BLOCK: For all monetary values: - Store and calculate prices as integers (cents) - Never use floating point for money calculations - Display prices using Intl.NumberFormat with the correct currency - Stripe expects amounts in cents — no conversion needed when stored correctly CODE_BLOCK: For all monetary values: - Store and calculate prices as integers (cents) - Never use floating point for money calculations - Display prices using Intl.NumberFormat with the correct currency - Stripe expects amounts in cents — no conversion needed when stored correctly CODE_BLOCK: For all monetary values: - Store and calculate prices as integers (cents) - Never use floating point for money calculations - Display prices using Intl.NumberFormat with the correct currency - Stripe expects amounts in cents — no conversion needed when stored correctly CODE_BLOCK: // Good: integer math + formatted display const priceInCents = 1999; const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(priceInCents / 100); // "$19.99" — always correct CODE_BLOCK: // Good: integer math + formatted display const priceInCents = 1999; const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(priceInCents / 100); // "$19.99" — always correct CODE_BLOCK: // Good: integer math + formatted display const priceInCents = 1999; const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(priceInCents / 100); // "$19.99" — always correct CODE_BLOCK: Organize e-commerce routes using Next.js route groups: - (shop) for browsing: products, categories, search - (checkout) for purchase flow: cart, shipping, payment, confirmation - (account) for user: profile, orders, wishlist Each group gets its own layout. Don't nest checkout UI inside the shop layout. CODE_BLOCK: Organize e-commerce routes using Next.js route groups: - (shop) for browsing: products, categories, search - (checkout) for purchase flow: cart, shipping, payment, confirmation - (account) for user: profile, orders, wishlist Each group gets its own layout. Don't nest checkout UI inside the shop layout. CODE_BLOCK: Organize e-commerce routes using Next.js route groups: - (shop) for browsing: products, categories, search - (checkout) for purchase flow: cart, shipping, payment, confirmation - (account) for user: profile, orders, wishlist Each group gets its own layout. Don't nest checkout UI inside the shop layout. CODE_BLOCK: For inventory management: - Always check inventory server-side before confirming a purchase - Use database transactions for inventory decrement + order creation - Show "Low stock" warnings when quantity falls below threshold - Never trust client-side quantity validation alone CODE_BLOCK: For inventory management: - Always check inventory server-side before confirming a purchase - Use database transactions for inventory decrement + order creation - Show "Low stock" warnings when quantity falls below threshold - Never trust client-side quantity validation alone CODE_BLOCK: For inventory management: - Always check inventory server-side before confirming a purchase - Use database transactions for inventory decrement + order creation - Show "Low stock" warnings when quantity falls below threshold - Never trust client-side quantity validation alone CODE_BLOCK: Every product page must include: - generateMetadata with title, description, Open Graph image, canonical URL - JSON-LD structured data (Product schema with price, availability, reviews) - Category pages need proper pagination metadata - No client-side-only rendering for any content search engines need to index CODE_BLOCK: Every product page must include: - generateMetadata with title, description, Open Graph image, canonical URL - JSON-LD structured data (Product schema with price, availability, reviews) - Category pages need proper pagination metadata - No client-side-only rendering for any content search engines need to index CODE_BLOCK: Every product page must include: - generateMetadata with title, description, Open Graph image, canonical URL - JSON-LD structured data (Product schema with price, availability, reviews) - Category pages need proper pagination metadata - No client-side-only rendering for any content search engines need to index - 5 .cursorrules That Actually Changed Cursor's Output - How to Write .cursorrules That Actually Work - I Tested Every Way .cursorrules Can Break