Skip to main content
rule

next-js — Cursor rule: what it does, how to install

Enforces Next.js App Router conventions in Cursor: defaults to Server Components, organizes code by feature, and restricts client boundaries.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/next-js.mdc
Open on GitHub

What next-js does

This rule guides Cursor when generating or editing files matching **/*.{js,jsx,ts,tsx} in Next.js projects. It enforces a feature-driven folder structure inside the app/ directory instead of grouping files by technical type. Shared primitives stay in dedicated top-level directories: components/, lib/, hooks/, types/, and public/.

Component design favors React Server Components wherever possible. The assistant keeps pages and layouts on the server, pushing the 'use client' boundary down to leaf components like buttons or inputs. Data fetching happens directly inside async server components with parallel Promise.all calls, while state-changing requests route through route.ts handlers. Using a raw <img> tag triggers a correction in favor of next/image.

Instant feedback relies on streaming via loading.tsx and Suspense. Webfonts run through next/font to eliminate layout shift. The configuration assumes modern Next.js with the app/ directory; projects stuck on the legacy Pages Router will encounter broken file paths and layout patterns.

What it helps with

  • structure routes, layouts, and hooks by feature inside app/
  • restrict 'use client' boundaries strictly to interactive leaf components
  • run parallel data fetches in Server Components using Promise.all
  • replace raw <img> elements with next/image

Example prompts for the agent

  • Scaffold a product detail route inside app/products/[id] with server data fetching and a client button
  • Refactor this dashboard page to use loading.tsx and Suspense for slow widgets
  • Create a POST route handler in app/api/cart/route.ts to handle cart updates

How to install next-js

Manually: put the files in place

.cursor/rules/next-js.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about next-js

How to install next-js in Cursor?

Save the rule content into .cursor/rules/next-js.mdc in your project root. Cursor activates it automatically across matching **/*.{js,jsx,ts,tsx} files.

What does the next-js rule do?

It guides the assistant to produce idiomatic Next.js code for the App Router. It prioritizes Server Components, organizes files by feature, and sets standards for data fetching and asset optimization.

How does the rule handle client interactivity?

It prohibits placing 'use client' on entire routes or page wrappers. Interactivity must stay isolated at the lowest possible leaf components in the tree to keep client bundle sizes minimal.

Source text

next-js.mdc · CC0-1.0
---
description: This guide defines definitive best practices for Next.js applications, focusing on the `app` directory, Server Components, performance, and maintainability. Follow these rules to build robust, scalable, and performant Next.js projects.
globs: **/*.{js,jsx,ts,tsx}
---
# next-js Best Practices

This document outlines the definitive best practices for developing Next.js applications. Adhering to these guidelines ensures consistent, performant, and maintainable code, leveraging Next.js's strengths for modern web development.

## 1. Code Organization and Structure

Always use the `app/` directory for new projects. Organize code by feature, not by type, to improve discoverability and cohesion.

### ✅ GOOD: Feature-Driven `app/` Directory

Group all related files for a feature (components, pages, layouts, hooks, types) within a single directory.

```tsx
// app/dashboard/page.tsx
export default function DashboardPage() { /* ... */ }

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) { /* ... */ }

// app/dashboard/components/DashboardOverview.tsx
export function DashboardOverview() { /* ... */ }

// app/dashboard/hooks/useDashboardData.ts
export function useDashboardData() { /* ... */ }
```

### ❌ BAD: Type-Driven `app/` Directory

Avoid scattering files of the same feature across different top-level type directories.

```tsx
// app/dashboard/page.tsx
// components/dashboard/DashboardOverview.tsx // Separated
// hooks/useDashboardData.ts // Separated
```

### Core Directories

*   **`app/`**: All route-related files (`page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `route.ts`).
*   **`components/`**: Reusable UI components that are *not* directly tied to a specific route.
*   **`lib/`**: Backend-agnostic utility functions, data access layers, and third-party integrations.
*   **`hooks/`**: Custom React hooks for reusable logic.
*   **`types/`**: Global TypeScript type definitions and interfaces.
*   **`public/`**: Static assets (images, fonts) that are served directly.

## 2. Component Architecture: Server Components First

Prioritize Server Components for all UI rendering. Use Client Components only when interactivity (state, effects, event handlers) is strictly required.

### ✅ GOOD: Server Component by Default

Server Components reduce client-side JavaScript bundles, improve initial page load, and enhance security by keeping sensitive logic on the server.

```tsx
// app/products/[id]/page.tsx (Server Component by default)
import { getProductDetails } from '@/lib/api';

export default async function ProductPage({ params }) {
  const product = await getProductDetails(params.id);
  return (
    <div>
      <h1>{product.name}</h1>
      {/* ... more server-rendered UI */}
      <AddToCartButton productId={product.id} /> {/* Client Component */}
    </div>
  );
}
```

### ✅ GOOD: "use client" Boundary as Low as Possible

Place the `"use client"` directive at the lowest possible point in your component tree. This ensures that only the interactive parts are client-rendered, keeping parent components as Server Components.

```tsx
// components/AddToCartButton.tsx
'use client'; // Only this component and its children are client-side

import { useState } from 'react';

export function AddToCartButton({ productId }) {
  const [quantity, setQuantity] = useState(1);
  // ... interactive logic
  return <button onClick={() => alert(`Added ${quantity} of ${productId}`)}>Add to Cart</button>;
}
```

### ❌ BAD: Overuse of "use client"

Don't mark entire feature folders or layouts as client components if only a small part needs interactivity. This unnecessarily increases client bundle size.

```tsx
// app/products/[id]/page.tsx (BAD: entire page marked client)
'use client'; // This makes the whole page a client component
import { useState, useEffect } from 'react'; // Even if only a small part needs it

export default function ProductPage({ params }) {
  // ...
  return <button>Add to Cart</button>;
}
```

## 3. Data Fetching

Fetch data directly in Server Components using `fetch` or a dedicated data access layer. Use Route Handlers for client-side mutations or when exposing a specific API endpoint.

### ✅ GOOD: Server Component Data Fetching

Directly fetch data in Server Components. `fetch` requests are automatically memoized and cached by Next.js.

```tsx
// app/dashboard/page.tsx
import { getUserProfile, getRecentOrders } from '@/lib/api';

export default async function DashboardPage() {
  // Data fetches in parallel
  const [user, orders] = await Promise.all([
    getUserProfile(),
    getRecentOrders(),
  ]);

  return (
    <div>
      <h2>Welcome, {user.name}</h2>
      <OrderList orders={orders} />
    </div>
  );
}
```

### ✅ GOOD: Route Handlers for Client-Side Mutations

Use `route.ts` for API endpoints that handle client-side data mutations (e.g., form submissions, API calls from client components).

```tsx
// app/api/cart/route.ts
import { NextResponse } from 'next/server';
import { addToCart } from '@/lib/cart';

export async function POST(request: Request) {
  const { productId, quantity } = await request.json();
  await addToCart(productId, quantity);
  return NextResponse.json({ success: true });
}
```

### ✅ GOOD: Streaming with `loading.tsx` and `Suspense`

Improve perceived performance by showing instant loading states for slow data fetches.

```tsx
// app/dashboard/loading.tsx
export default function Loading() {
  return <div>Loading dashboard...</div>;
}

// app/dashboard/page.tsx (assuming some slow component)
import { Suspense } from 'react';
import { SlowComponent } from './components/SlowComponent';

export default async function DashboardPage() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading slow data...</div>}>
        <SlowComponent />
      </Suspense>
    </main>
  );
}
```

## 4. Performance Considerations

Leverage Next.js's built-in optimizations for images, fonts, and code splitting.

### ✅ GOOD: `next/image` for Images

Always use `next/image` for local and remote images. It provides automatic optimization, lazy loading, and responsive sizing.

```tsx
import Image from 'next/image';
import profilePic from '@/public/profile.jpg';

export function UserAvatar() {
  return (
    <Image
      src={profilePic}
      alt="User Profile"
      width={100}
      height={100}
      placeholder="blur"
    />
  );
}
```

### ❌ BAD: Native `<img>` Tag

Avoid the native `<img>` tag as it bypasses Next.js's image optimizations.

```tsx
// ❌ BAD
export function UserAvatar() {
  return <img src="/profile.jpg" alt="User Profile" width="100" height="100" />;
}
```

### ✅ GOOD: `next/font` for Fonts

Use `next/font` to optimize font loading, eliminate external network requests, and prevent layout shift.

```tsx
import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  );
}
```

### ✅ GOOD: Dynamic Imports for Heavy Components

Lazily load heavy client-side components or third-party libraries using `next/dynamic`.

```tsx
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('./components/HeavyChart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false, // Only load on client if not needed for initial render
});

export function DashboardCharts() {
  return (
    <div>
      <HeavyChart />
    </div>
  );
}
```

## 5. State Management

Keep state local where possible. For global state, use React Context for simple cases or lightweight libraries like Zustand/Jotai for more complex needs.

### ✅ GOOD: Local State with `useState`

For component-specific, ephemeral state.

```tsx
'use client';
import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
```

### ✅ GOOD: Global State with Context or Zustand/Jotai

For application-wide state that needs to be shared across many components. Prefer Zustand or Jotai over Redux for most Next.js projects due to their simplicity and performance.

```tsx
// lib/store.ts (using Zustand)
import { create } from 'zustand';

interface BearState {
  bears: number;
  increasePopulation: () => void;
}

export const useBearStore = create<BearState>((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}));
```

## 6. Error Handling

Implement robust error handling using Next.js's dedicated error files.

### ✅ GOOD: Route-Level `error.tsx`

Catch errors within a specific route segment, providing localized fallback UI.

```tsx
// app/dashboard/error.tsx
'use client'; // Error boundaries must be client components

import { useEffect } from 'react';

export default function Error({ error, reset }) {
  useEffect(() => {
    console.error(error); // Log the error to an error reporting service
  }, [error]);

  return (
    <div>
      <h2>Something went wrong in the dashboard!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}
```

### ✅ GOOD: Global `global-error.tsx`

Catch uncaught errors across your entire application, providing a consistent fallback.

```tsx
// app/global-error.tsx
'use client';

export default function GlobalError({ error, reset }) {
  return (
    <html>
      <body>
        <h2>Something went wrong globally!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}
```

### ✅ GOOD: `not-found.tsx` for 404s

Create a custom 404 page for unmatched routes.

```tsx
// app/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <p>The page you are looking for does not exist.</p>
    </div>
  );
}
```

## 7. ESLint and Type Checking

Enforce code quality and catch common issues early with ESLint and TypeScript.

### ✅ GOOD: Use `eslint-config-next/core-web-vitals`

This configuration elevates performance-related warnings to errors, ensuring your application meets Core Web Vitals standards. Always combine with `eslint-config-next/typescript` for TypeScript projects.

```javascript
// eslint.config.mjs
import { defineConfig, globalIgnores } from 'eslint/config';
import nextVitals from 'eslint-config-next/core-web-vitals';
import nextTs from 'eslint-config-next/typescript'; // For TypeScript projects

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs, // Include for TypeScript
  {
    rules: {
      // Custom overrides or additional rules here
      '@next/next/no-img-element': 'error', // Enforce next/image
      // ...
    },
  },
  globalIgnores([
    '.next/**',
    'out/**',
    'build/**',
    'next-env.d.ts',
  ]),
]);

export default eslintConfig;
```

## 8. Common Pitfalls and Anti-patterns

Avoid these common mistakes to maintain a high-quality Next.js application.

### ❌ BAD: Using `<a>` for Internal Navigation

This bypasses Next.js's automatic prefetching and client-side navigation.

```tsx
// ❌ BAD
<a href="/dashboard">Go to Dashboard</a>
```

### ✅ GOOD: Use `<Link>` for Internal Navigation

Enables client-side navigation and prefetching for a smoother user experience.

```tsx
import Link from 'next/link';

// ✅ GOOD
<Link href="/dashboard">Go to Dashboard</Link>
```

### ❌ BAD: Async Client Components

Client Components cannot be `async`. If you need to fetch data on the client, use `useEffect` or a client-side data fetching library.

```tsx
// components/MyClientComponent.tsx
'use client';

// ❌ BAD: Client Components cannot be async
export default async function MyClientComponent() {
  // const data = await fetch('/api/data');
  return <div>Client UI</div>;
}
```

### ✅ GOOD: Client-Side Data Fetching in Client Components

Use `useEffect` or a dedicated client-side library (like SWR or React Query) for data fetching in Client Components.

```tsx
// components/MyClientComponent.tsx
'use client';
import { useState, useEffect } from 'react';

export default function MyClientComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    async function fetchData() {
      const res = await fetch('/api/data');
      const json = await res.json();
      setData(json);
    }
    fetchData();
  }, []);

  return <div>{data ? `Data: ${data.message}` : 'Loading...'}</div>;
}
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

All IDE rules for Frontend and UI →

From the same set240

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor