Tools
Tools: The .cursorrules I'd Actually Use for a Next.js E-Commerce Project
2026-02-13
0 views
admin
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 hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse 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 Enter fullscreen mode Exit fullscreen mode 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> );
} Enter fullscreen mode Exit fullscreen mode 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
} Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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...
} Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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. Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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
how-totutorialguidedev.toaiserverdatabasegitgithub