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

patterns.mddocs/

Next.js Implementation Patterns

Production-ready patterns for common use cases.

Authentication

Server-Side Auth Check

// app/dashboard/page.tsx
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

export default async function Dashboard() {
  const cookieStore = await cookies();
  const token = cookieStore.get('token');
  
  if (!token) redirect('/login');
  
  const user = await getUser(token.value);
  return <div>Welcome {user.name}</div>;
}

Middleware Auth

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

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

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
};

Data Fetching

Parallel Data Fetching

export default async function Dashboard() {
  const [user, posts, analytics] = await Promise.all([
    getUser(),
    getPosts(),
    getAnalytics(),
  ]);
  
  return (
    <div>
      <UserProfile user={user} />
      <PostsList posts={posts} />
      <Analytics data={analytics} />
    </div>
  );
}

Streaming with Suspense

import { Suspense } from 'react';

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

Form Handling

Server Actions

// app/actions.ts
'use server';
import { revalidatePath, redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
  const title = formData.get('title');
  const post = await savePost({ title });
  
  revalidatePath('/posts');
  redirect(`/posts/${post.id}`);
}

// app/posts/create/page.tsx
import { createPost } from '../actions';

export default function CreatePost() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  );
}

Optimistic Updates

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

export default function LikeButton({
  postId,
  initialCount
}: {
  postId: string;
  initialCount: number;
}) {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState<number>(initialCount);

  function handleLike() {
    startTransition(async () => {
      setCount(prev => prev + 1); // Optimistic
      await likePost(postId);
    });
  }

  return (
    <button onClick={handleLike} disabled={isPending}>
      Like ({count})
    </button>
  );
}

Search & Filters

URL Search Params

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

export default function SearchBar() {
  const router = useRouter();
  const searchParams = useSearchParams();
  
  function handleSearch(query: string) {
    const params = new URLSearchParams(searchParams.toString());
    params.set('q', query);
    router.push(`/search?${params.toString()}`);
  }
  
  return (
    <input
      onChange={(e) => handleSearch(e.target.value)}
      defaultValue={searchParams.get('q') || ''}
    />
  );
}

Server-Side Search

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

export default async function SearchPage({
  searchParams,
}: {
  searchParams: Promise<{ q?: string }>;
}) {
  const { q } = await searchParams;
  const results: SearchResult[] = q ? await searchItems(q) : [];

  return <SearchResults results={results} />;
}

Caching & Revalidation

On-Demand Revalidation

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const { secret, path } = await request.json();
  
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ error: 'Invalid' }, { status: 401 });
  }
  
  revalidatePath(path);
  return NextResponse.json({ revalidated: true });
}

Tag-Based Revalidation

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

// Revalidate in Server Action
'use server';
import { revalidateTag } from 'next/cache';

export async function updatePost(postId: string) {
  await savePost(postId);
  revalidateTag(`post-${postId}`);
}

API Routes

REST API

// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const user = await getUser(id);
  
  if (!user) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }
  
  return NextResponse.json(user);
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const body = await request.json();
  const user = await updateUser(id, body);
  return NextResponse.json(user);
}

Image Patterns

Responsive Images

import Image from 'next/image';

<Image
  src="/photo.jpg"
  alt="Photo"
  width={800}
  height={600}
  sizes="(max-width: 768px) 100vw, 50vw"
  priority
/>

Dynamic Images from API

import Image from 'next/image';

export default async function PostImage({ imageUrl }: { imageUrl: string }) {
  return (
    <Image
      src={imageUrl}
      alt="Post image"
      width={800}
      height={600}
    />
  );
}

Loading States

Page-Level Loading

// app/posts/loading.tsx
export default function Loading() {
  return <div className="animate-pulse">Loading...</div>;
}

Skeleton Screens

export default function PostSkeleton() {
  return (
    <div className="animate-pulse space-y-4">
      <div className="h-8 bg-gray-200 rounded w-3/4"></div>
      <div className="h-4 bg-gray-200 rounded w-full"></div>
    </div>
  );
}

Error Boundaries

// app/error.tsx
'use client';
import { useEffect } from 'react';

export default function Error({
  error,
  reset
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error('Error:', error);
  }, [error]);

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

docs

api.md

concepts.md

index.md

pages-router.md

patterns.md

special-files.md

tile.json