CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-next

The React Framework for production-grade applications with server-side rendering, static site generation, and full-stack capabilities

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

concepts.mddocs/

Next.js Core Concepts

App Router vs Pages Router

App Router (Default) - Recommended for new projects

  • React Server Components (default)
  • Nested layouts with state preservation
  • Server Actions for mutations
  • Streaming and Suspense
  • File-based routing in app/

Pages Router (Legacy) - Use for existing apps

  • Traditional file-based routing in pages/
  • API routes in pages/api
  • Team familiarity with Pages conventions

Decision: New project? → App Router | Existing Pages app? → Continue with Pages

Server vs Client Components

Server Components (Default)

Use when:

  • Fetching data from DB/API
  • Accessing backend resources
  • Sensitive information (API keys)
  • Heavy dependencies (exclude from client bundle)

Characteristics:

  • Run on server
  • Can be async
  • No React hooks
  • Not in client bundle

Client Components

Use when:

  • Interactivity (onClick, onChange)
  • Browser APIs (window, localStorage)
  • React hooks (useState, useEffect)
  • Event listeners

Characteristics:

  • Run in browser
  • Requires 'use client'
  • Can use all React features
// Server Component (default)
export default async function Page() {
  const data = await fetch('https://api.example.com/posts');
  const posts = await data.json();
  return <div>{JSON.stringify(posts)}</div>;
}

// Client Component
'use client';
import { useState } from 'react';

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

Data Fetching Strategies

1. ⚡ Static Generation (SSG) - Fastest

When: Content doesn't change often Performance: Cached at build time, served from CDN edge

export const revalidate = false; // Infinite cache

export default async function Page() {
  const data = await fetch('https://api.example.com/posts');
  return <div>{await data.json()}</div>;
}

2. ⚡ Incremental Static Regeneration (ISR) - Great Performance

When: Content changes but doesn't need to be real-time Performance: Cached with periodic revalidation, mostly edge-served

export const revalidate = 60; // Revalidate every 60s

export default async function Page() {
  const data = await fetch('https://api.example.com/posts');
  return <div>{await data.json()}</div>;
}

3. ⚠️ Server-Side Rendering (SSR) - Slower

When: User-specific or frequently changing content Performance: Generated on every request, no caching

export const dynamic = 'force-dynamic';

export default async function Page() {
  const data = await fetch('https://api.example.com/posts', {
    cache: 'no-store'
  });
  return <div>{await data.json()}</div>;
}

4. 🔄 Client-Side Data Fetching - Additional Request

When: User interactions trigger data fetching Performance: Requires separate network request, adds latency

'use client';
import { useEffect, useState } from 'react';

interface SearchResult {
  id: string;
  title: string;
}

export default function Search() {
  const [results, setResults] = useState<SearchResult[]>([]);

  useEffect(() => {
    fetch('/api/search')
      .then(res => res.json())
      .then((data: SearchResult[]) => setResults(data));
  }, []);

  return <div>{results.map(r => r.title).join(', ')}</div>;
}

5. 🔄 Streaming with Suspense - Progressive Rendering

When: Want to show parts of page while others load Performance: Reduces Time to First Byte, improves perceived performance

import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

Caching Strategies

Request Deduplication

Automatic - multiple identical requests are deduplicated

Time-Based Revalidation

const res = await fetch('https://api.example.com/data', {
  next: { revalidate: 3600 } // 1 hour
});

Tag-Based Revalidation

// Fetch with tag
const posts = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
});

// Revalidate by tag
import { revalidateTag } from 'next/cache';
revalidateTag('posts');

On-Demand Revalidation

import { revalidatePath } from 'next/cache';
revalidatePath('/posts');

Route File Conventions

app/
  page.tsx          → /
  about/page.tsx    → /about
  blog/[slug]/page.tsx → /blog/[slug]
  (marketing)/about/page.tsx → /about (route group)

Dynamic Segments

[id]           → Single dynamic segment
[...slug]       → Catch-all
[[...slug]]    → Optional catch-all

Route Groups

(marketing)/about/page.tsx  → /about (parentheses not in URL)

Parallel Routes

app/
  layout.tsx
  @analytics/
    page.tsx    → Analytics slot

Navigation Patterns

Client-Side

'use client';
import { useRouter, usePathname } from 'next/navigation';

const router = useRouter();
router.push('/about');

Server-Side

import { redirect } from 'next/navigation';

export default async function Page() {
  const user = await getUser();
  if (!user) redirect('/login');
  return <div>Content</div>;
}

Error Handling

// app/error.tsx
'use client';
export default function Error({
  error,
  reset
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>Error: {error.message}</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// app/not-found.tsx
import { notFound } from 'next/navigation';

export default async function Post({
  params
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params;
  const post = await getPost(id);
  if (!post) notFound();
  return <article>{post.content}</article>;
}

Metadata & SEO

import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Page Title',
  description: 'Description',
  openGraph: {
    title: 'OG Title',
    images: ['/og-image.jpg'],
  },
};

// Dynamic
export async function generateMetadata({
  params
}: {
  params: Promise<{ id: string }>
}): Promise<Metadata> {
  const { id } = await params;
  const post = await getPost(id);
  return { title: post.title };
}

Performance Optimization

Image

import Image from 'next/image';

<Image src="/photo.jpg" alt="Photo" width={800} height={600} priority />

Font

import { Inter } from 'next/font/google';
import type { ReactNode } from 'react';

const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });

export default function Layout({ children }: { children: ReactNode }) {
  return <html className={inter.variable}><body>{children}</body></html>;
}

Script

import Script from 'next/script';

<Script src="https://example.com/script.js" strategy="afterInteractive" />

Middleware

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token');
  
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  return NextResponse.next();
}

export const config = { matcher: '/dashboard/:path*' };

Key Decisions

  1. App Router or Pages? → App Router for new projects
  2. Server or Client? → Server by default, Client when needed
  3. Static or Dynamic? → Static for performance, Dynamic for fresh data
  4. Time or Tag revalidation? → Time for predictable, Tag for user-generated
  5. Link or router.push? → Link for navigation, router.push for programmatic

docs

api.md

concepts.md

index.md

pages-router.md

patterns.md

special-files.md

tile.json