Saltar a contenido

Siguiente.js Cheatsheet

■h1 títuloSiguiente.js - El Marco de Reacción para la Producción "Clase de inscripción" Next.js es un marco React que le da bloques de construcción para crear aplicaciones web. Por marco, nos referimos a Next.js maneja la herramienta y configuración necesarias para React, y proporciona estructura adicional, características y optimizaciones para su aplicación. ▪/p] ■/div titulada

########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button

■/div titulada ■/div titulada

Cuadro de contenidos

Instalación

Prerrequisitos

# Node.js version requirements
node --version  # Should be 16.8 or later

# Check npm version
npm --version

# Update npm if needed
npm install -g npm@latest

Crear Siguiente.js App

# Create new Next.js app
npx create-next-app@latest my-app

# Create with TypeScript
npx create-next-app@latest my-app --typescript

# Create with specific template
npx create-next-app@latest my-app --example with-tailwindcss

# Interactive creation
npx create-next-app@latest

Instalación manual

# Create project directory
mkdir my-nextjs-app
cd my-nextjs-app

# Initialize package.json
npm init -y

# Install Next.js, React, and React DOM
npm install next react react-dom

# Install TypeScript (optional)
npm install --save-dev typescript @types/react @types/node

Paquete.json Scripts

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "export": "next export"
  }
}

Creación de proyectos

Plantillas disponibles

# Official examples
npx create-next-app@latest --example api-routes
npx create-next-app@latest --example with-typescript
npx create-next-app@latest --example with-tailwindcss
npx create-next-app@latest --example with-styled-components
npx create-next-app@latest --example with-material-ui
npx create-next-app@latest --example with-redux
npx create-next-app@latest --example with-apollo
npx create-next-app@latest --example with-prisma
npx create-next-app@latest --example with-supabase
npx create-next-app@latest --example with-firebase

Development Server

# Start development server
npm run dev

# Start on specific port
npm run dev -- -p 3001

# Start with turbo mode (faster builds)
npm run dev -- --turbo

# Start with experimental features
npm run dev -- --experimental-app

Construcción y producción

# Build for production
npm run build

# Start production server
npm run start

# Export static site
npm run export

# Analyze bundle
npm run build -- --analyze

Estructura del proyecto

App Router Structure (Next.js 13+)

my-nextjs-app/
├── app/                    # App Router (new)
│   ├── globals.css         # Global styles
│   ├── layout.tsx          # Root layout
│   ├── page.tsx           # Home page
│   ├── loading.tsx        # Loading UI
│   ├── error.tsx          # Error UI
│   ├── not-found.tsx      # 404 page
│   ├── about/
│   │   └── page.tsx       # /about route
│   ├── blog/
│   │   ├── page.tsx       # /blog route
│   │   └── [slug]/
│   │       └── page.tsx   # /blog/[slug] route
│   └── api/
│       └── users/
│           └── route.ts   # API endpoint
├── components/             # Reusable components
├── lib/                   # Utility functions
├── public/                # Static assets
├── styles/                # Additional styles
├── next.config.js         # Next.js configuration
├── package.json
└── tsconfig.json

Estructura del Router (Legacy)

my-nextjs-app/
├── pages/                 # Pages Router (legacy)
│   ├── _app.tsx          # Custom App component
│   ├── _document.tsx     # Custom Document
│   ├── index.tsx         # Home page (/)
│   ├── about.tsx         # About page (/about)
│   ├── blog/
│   │   ├── index.tsx     # Blog index (/blog)
│   │   └── [slug].tsx    # Dynamic route (/blog/[slug])
│   └── api/
│       └── users.ts      # API endpoint (/api/users)
├── components/
├── lib/
├── public/
├── styles/
├── next.config.js
└── package.json

Routing

App Router (Next.js 13+)

Routing basado en archivos

# Route structure
app/
├── page.tsx              # / (root)
├── about/page.tsx        # /about
├── blog/
│   ├── page.tsx          # /blog
│   └── [slug]/page.tsx   # /blog/[slug]
├── shop/
│   └── [category]/
│       └── [product]/
│           └── page.tsx  # /shop/[category]/[product]
└── (dashboard)/          # Route group (doesn't affect URL)
    ├── analytics/page.tsx # /analytics
    └── settings/page.tsx  # /settings

Rutas dinámicas

// app/blog/[slug]/page.tsx
interface PageProps {
  params: { slug: string };
| searchParams: { [key: string]: string | string[] | undefined }; |
}

export default function BlogPost({ params, searchParams }: PageProps) {
  return <h1>Post: {params.slug}</h1>;
}

// Generate static params
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(res => res.json());

  return posts.map((post: any) => ({
    slug: post.slug,
  }));
}

Recorridos

// app/shop/[...slug]/page.tsx
interface PageProps {
  params: { slug: string[] };
}

export default function Shop({ params }: PageProps) {
  // /shop/a/b/c -> params.slug = ['a', 'b', 'c']
  return <div>Shop: {params.slug.join('/')}</div>;
}
// Client-side navigation
import Link from 'next/link';
import { useRouter } from 'next/navigation';

function Navigation() {
  const router = useRouter();

  return (
    <nav>
      {/* Static link */}
      <Link href="/about">About</Link>

      {/* Dynamic link */}
      <Link href={`/blog/${post.slug}`}>Read Post</Link>

      {/* Programmatic navigation */}
      <button onClick={() => router.push('/dashboard')}>
        Go to Dashboard
      </button>

      {/* Replace current entry */}
      <button onClick={() => router.replace('/login')}>
        Login
      </button>

      {/* Go back */}
      <button onClick={() => router.back()}>
        Go Back
      </button>
    </nav>
  );
}

Grupos de ruta y rutas paralelas

# Route groups (don't affect URL structure)
app/
├── (marketing)/
│   ├── about/page.tsx     # /about
│   └── contact/page.tsx   # /contact
├── (shop)/
│   ├── products/page.tsx  # /products
│   └── cart/page.tsx      # /cart
└── page.tsx              # /

# Parallel routes
app/
├── @analytics/
│   └── page.tsx
├── @team/
│   └── page.tsx
├── layout.tsx            # Can render both slots
└── page.tsx

Páginas y diseños

Root Layout

// app/layout.tsx
import './globals.css';

export const metadata = {
  title: 'My App',
  description: 'Generated by Next.js',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <header>
          <nav>Navigation</nav>
        </header>
        <main>{children}</main>
        <footer>Footer</footer>
      </body>
    </html>
  );
}

Diseños anidados

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <aside>
        <nav>Dashboard Navigation</nav>
      </aside>
      <main>{children}</main>
    </div>
  );
}

Componentes de página

// app/page.tsx
export default function HomePage() {
  return (
    <div>
      <h1>Welcome to Next.js!</h1>
      <p>This is the home page.</p>
    </div>
  );
}

// With metadata
export const metadata = {
  title: 'Home | My App',
  description: 'Welcome to my Next.js application',
};

Estados de carga y error

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

// app/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="error">
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// app/not-found.tsx
export default function NotFound() {
  return (
    <div className="not-found">
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
    </div>
  );
}

API Routes

App Router API Routes

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

// GET /api/users
export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const query = searchParams.get('query');

  // Fetch users from database
  const users = await fetchUsers(query);

  return NextResponse.json(users);
}

// POST /api/users
export async function POST(request: NextRequest) {
  const body = await request.json();

  // Create user
  const user = await createUser(body);

  return NextResponse.json(user, { status: 201 });
}

// PUT /api/users
export async function PUT(request: NextRequest) {
  const body = await request.json();

  // Update user
  const user = await updateUser(body);

  return NextResponse.json(user);
}

// DELETE /api/users
export async function DELETE(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get('id');

  await deleteUser(id);

  return new NextResponse(null, { status: 204 });
}

Rutas dinámicas de API

// app/api/users/[id]/route.ts
interface RouteParams {
  params: { id: string };
}

export async function GET(
  request: NextRequest,
  { params }: RouteParams
) {
  const user = await fetchUser(params.id);

  if (!user) {
    return new NextResponse('User not found', { status: 404 });
  }

  return NextResponse.json(user);
}

export async function DELETE(
  request: NextRequest,
  { params }: RouteParams
) {
  await deleteUser(params.id);
  return new NextResponse(null, { status: 204 });
}

Middleware

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

export function middleware(request: NextRequest) {
  // Authentication check
  const token = request.cookies.get('token');

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // Add custom headers
  const response = NextResponse.next();
  response.headers.set('X-Custom-Header', 'value');

  return response;
}

export const config = {
  matcher: [
| '/((?!api | _next/static | _next/image | favicon.ico).*)', |
  ],
};

Manejo de errores de API

// lib/api-error.ts
export class ApiError extends Error {
  constructor(
    public statusCode: number,
    message: string,
    public code?: string
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

// app/api/users/route.ts
export async function GET() {
  try {
    const users = await fetchUsers();
    return NextResponse.json(users);
  } catch (error) {
    if (error instanceof ApiError) {
      return NextResponse.json(
        { error: error.message, code: error.code },
        { status: error.statusCode }
      );
    }

    return NextResponse.json(
      { error: 'Internal Server Error' },
      { status: 500 }
    );
  }
}

Datos obtenidos

Componentes de servidor (por defecto)

// app/posts/page.tsx
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    // Revalidate every hour
    next: { revalidate: 3600 }
  });

  if (!res.ok) {
    throw new Error('Failed to fetch posts');
  }

  return res.json();
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <div>
      <h1>Posts</h1>
      {posts.map((post: any) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

Componentes del cliente

// components/PostList.tsx
'use client';

import { useState, useEffect } from 'react';

export default function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchPosts() {
      try {
        const res = await fetch('/api/posts');
        const data = await res.json();
        setPosts(data);
      } catch (error) {
        console.error('Error fetching posts:', error);
      } finally {
        setLoading(false);
      }
    }

    fetchPosts();
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      {posts.map((post: any) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
        </article>
      ))}
    </div>
  );
}

Static Site Generation (SSG)

// app/posts/[slug]/page.tsx
interface Post {
  slug: string;
  title: string;
  content: string;
}

// Generate static params at build time
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(res => res.json());

  return posts.map((post: Post) => ({
    slug: post.slug,
  }));
}

// Generate metadata
export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(res => res.json());

  return {
    title: post.title,
    description: post.excerpt,
  };
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(res => res.json());

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Regeneración Estatica Incremental (ISR)

// Revalidate every 60 seconds
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 60 }
  });

  return res.json();
}

// On-demand revalidation
export async function POST(request: NextRequest) {
  const secret = request.nextUrl.searchParams.get('secret');

  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
  }

  try {
    await revalidatePath('/posts');
    return NextResponse.json({ revalidated: true });
  } catch (err) {
    return NextResponse.json({ message: 'Error revalidating' }, { status: 500 });
  }
}

Styling

CSS Módulos

/* styles/Home.module.css */
.container {
  padding: 0 2rem;
}

.main {
  min-height: 100vh;
  padding: 4rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.title {
  margin: 0;
  line-height: 1.15;
  font-size: 4rem;
  text-align: center;
}
// components/Home.tsx
import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <h1 className={styles.title}>Welcome to Next.js!</h1>
      </main>
    </div>
  );
}

Tailwind CSS

# Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
        secondary: '#64748b',
      },
    },
  },
  plugins: [],
};
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-primary {
    @apply bg-primary text-white px-4 py-2 rounded hover:bg-blue-600;
  }
}

Componentes de estilo

# Install styled-components
npm install styled-components
npm install -D @types/styled-components
// components/Button.tsx
import styled from 'styled-components';

const StyledButton = styled.button<{ variant?: 'primary' | 'secondary' }>`
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 0.25rem;
  cursor: pointer;
  font-weight: 500;

  ${props => props.variant === 'primary' && `
    background-color: #3b82f6;
    color: white;

    &:hover {
      background-color: #2563eb;
    }
  `}

  ${props => props.variant === 'secondary' && `
    background-color: #6b7280;
    color: white;

    &:hover {
      background-color: #4b5563;
    }
  `}
`;

export default function Button({ children, variant = 'primary', ...props }) {
  return (
    <StyledButton variant={variant} {...props}>
      {children}
    </StyledButton>
  );
}

CSS-en-JS con Emoción

# Install Emotion
npm install @emotion/react @emotion/styled
// components/Card.tsx
import styled from '@emotion/styled';
import { css } from '@emotion/react';

const Card = styled.div`
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 1rem;
  margin: 1rem 0;
`;

const cardTitle = css`
  font-size: 1.5rem;
  font-weight: bold;
  margin-bottom: 0.5rem;
  color: #1f2937;
`;

export default function ProductCard({ title, description }) {
  return (
    <Card>
      <h3 css={cardTitle}>{title}</h3>
      <p>{description}</p>
    </Card>
  );
}

Optimización de imagen

Next.js Image Component

import Image from 'next/image';

export default function Gallery() {
  return (
    <div>
      {/* Static image */}
      <Image
        src="/hero.jpg"
        alt="Hero image"
        width={800}
        height={600}
        priority // Load immediately
      />

      {/* Dynamic image */}
      <Image
        src={`https://api.example.com/images/${imageId}`}
        alt="Dynamic image"
        width={400}
        height={300}
        placeholder="blur"
        blurDataURL="data:image/jpeg;base64,..."
      />

      {/* Responsive image */}
      <Image
        src="/responsive.jpg"
        alt="Responsive image"
        fill
        style={{ objectFit: 'cover' }}
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      />
    </div>
  );
}

Configuración de imagen

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    domains: ['example.com', 'api.example.com'],
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.example.com',
        port: '',
        pathname: '/images/**',
      },
    ],
    formats: ['image/webp', 'image/avif'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
};

module.exports = nextConfig;

Cargador de imagen personalizado

// lib/imageLoader.ts
export default function cloudinaryLoader({ src, width, quality }) {
| const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality |  | 'auto'}`]; |
  return `https://res.cloudinary.com/demo/image/fetch/${params.join(',')}/${src}`;
}

// Usage
<Image
  loader={cloudinaryLoader}
  src="/sample.jpg"
  alt="Sample"
  width={500}
  height={300}
/>

Optimización del rendimiento

Bundle Analysis

# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer

# Configure in next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
  // Your Next.js config
});

# Analyze bundle
ANALYZE=true npm run build

Código de división

// Dynamic imports
import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), {
  loading: () => <p>Loading...</p>,
  ssr: false, // Disable server-side rendering
});

// Conditional loading
const ConditionalComponent = dynamic(
  () => import('../components/ConditionalComponent'),
  { ssr: false }
);

export default function Page() {
  const [showComponent, setShowComponent] = useState(false);

  return (
    <div>
      <button onClick={() => setShowComponent(true)}>
        Load Component
      </button>
      {showComponent && <ConditionalComponent />}
    </div>
  );
}

Lazy Cargando

import { lazy, Suspense } from 'react';

const LazyChart = lazy(() => import('../components/Chart'));

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading chart...</div>}>
        <LazyChart />
      </Suspense>
    </div>
  );
}

Supervisión de la ejecución

// lib/analytics.ts
export function reportWebVitals(metric) {
  switch (metric.name) {
    case 'CLS':
    case 'FID':
    case 'FCP':
    case 'LCP':
    case 'TTFB':
      // Send to analytics service
      analytics.track('Web Vital', {
        name: metric.name,
        value: metric.value,
        id: metric.id,
      });
      break;
    default:
      break;
  }
}

// app/layout.tsx
import { reportWebVitals } from '../lib/analytics';

export { reportWebVitals };

Estrategias de producción

// Static data caching
const getData = cache(async () => {
  const res = await fetch('https://api.example.com/data');
  return res.json();
});

// Request memoization
import { unstable_cache } from 'next/cache';

const getCachedData = unstable_cache(
  async (id) => {
    const data = await fetchData(id);
    return data;
  },
  ['data-cache'],
  { revalidate: 3600 } // 1 hour
);
```_

## Autenticación

### NextAuth.js Setup
```bash
# Install NextAuth.js
npm install next-auth
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import CredentialsProvider from 'next-auth/providers/credentials';

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' }
      },
      async authorize(credentials) {
        // Verify credentials
        const user = await verifyCredentials(credentials);
        return user ? { id: user.id, email: user.email } : null;
      }
    })
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      session.user.id = token.id;
      return session;
    },
  },
  pages: {
    signIn: '/auth/signin',
    signUp: '/auth/signup',
  },
});

export { handler as GET, handler as POST };

Gestión del período de sesiones

// components/AuthProvider.tsx
'use client';

import { SessionProvider } from 'next-auth/react';

export default function AuthProvider({
  children,
  session,
}: {
  children: React.ReactNode;
  session: any;
}) {
  return <SessionProvider session={session}>{children}</SessionProvider>;
}

// app/layout.tsx
import AuthProvider from '../components/AuthProvider';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AuthProvider>
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}

Rutas protegidas

// components/ProtectedRoute.tsx
import { useSession } from 'next-auth/react';
import { redirect } from 'next/navigation';

export default function ProtectedRoute({
  children,
}: {
  children: React.ReactNode;
}) {
  const { data: session, status } = useSession();

  if (status === 'loading') {
    return <div>Loading...</div>;
  }

  if (!session) {
    redirect('/auth/signin');
  }

  return <>{children}</>;
}

// Server-side protection
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';

export default async function ProtectedPage() {
  const session = await getServerSession();

  if (!session) {
    redirect('/auth/signin');
  }

  return <div>Protected content</div>;
}

Integración de bases de datos

Prisma Setup

# Install Prisma
npm install prisma @prisma/client
npx prisma init
```_

```prisma
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

Operaciones de base de datos

// lib/posts.ts
import { prisma } from './prisma';

export async function getPosts() {
  return await prisma.post.findMany({
    include: {
      author: {
        select: {
          name: true,
          email: true,
        },
      },
    },
    orderBy: {
      createdAt: 'desc',
    },
  });
}

export async function createPost(data: {
  title: string;
  content: string;
  authorId: string;
}) {
  return await prisma.post.create({
    data,
    include: {
      author: true,
    },
  });
}

export async function updatePost(id: string, data: {
  title?: string;
  content?: string;
  published?: boolean;
}) {
  return await prisma.post.update({
    where: { id },
    data,
  });
}

export async function deletePost(id: string) {
  return await prisma.post.delete({
    where: { id },
  });
}

API Integration

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getPosts, createPost } from '../../../lib/posts';

export async function GET() {
  try {
    const posts = await getPosts();
    return NextResponse.json(posts);
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch posts' },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const post = await createPost(body);
    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to create post' },
      { status: 500 }
    );
  }
}

Despliegue

Despliegue de Vercel

# Install Vercel CLI
npm install -g vercel

# Deploy to Vercel
vercel

# Deploy to production
vercel --prod

# Set environment variables
vercel env add VARIABLE_NAME
// vercel.json
{
  "framework": "nextjs",
  "buildCommand": "npm run build",
  "devCommand": "npm run dev",
  "installCommand": "npm install",
  "functions": {
    "app/api/**/*.ts": {
      "maxDuration": 30
    }
  },
  "regions": ["iad1"],
  "env": {
    "DATABASE_URL": "@database-url"
  }
}

Docker Deployment

# Dockerfile
FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \
  fi

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN yarn build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]

Static Export

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  trailingSlash: true,
  images: {
    unoptimized: true
  }
};

module.exports = nextConfig;
# Build and export
npm run build

# Serve static files
npx serve out

Pruebas

Biblioteca de Pruebas Jest y Reacting

# Install testing dependencies
npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom
// jest.config.js
const nextJest = require('next/jest');

const createJestConfig = nextJest({
  dir: './',
});

const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapping: {
    '^@/components/(.*)$': '<rootDir>/components/$1',
    '^@/pages/(.*)$': '<rootDir>/pages/$1',
  },
  testEnvironment: 'jest-environment-jsdom',
};

module.exports = createJestConfig(customJestConfig);
// jest.setup.js
import '@testing-library/jest-dom';

Pruebas de componentes

// __tests__/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '../components/Button';

describe('Button', () => {
  it('renders button with text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button')).toHaveTextContent('Click me');
  });

  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);

    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('applies correct variant styles', () => {
    render(<Button variant="primary">Primary</Button>);
    expect(screen.getByRole('button')).toHaveClass('btn-primary');
  });
});

API Testing

// __tests__/api/users.test.ts
import { createMocks } from 'node-mocks-http';
import handler from '../../pages/api/users';

describe('/api/users', () => {
  it('returns users list', async () => {
    const { req, res } = createMocks({
      method: 'GET',
    });

    await handler(req, res);

    expect(res._getStatusCode()).toBe(200);
    const data = JSON.parse(res._getData());
    expect(Array.isArray(data)).toBe(true);
  });

  it('creates new user', async () => {
    const { req, res } = createMocks({
      method: 'POST',
      body: {
        name: 'John Doe',
        email: 'john@example.com',
      },
    });

    await handler(req, res);

    expect(res._getStatusCode()).toBe(201);
    const data = JSON.parse(res._getData());
    expect(data.name).toBe('John Doe');
  });
});

E2E Pruebas con Playwright

# Install Playwright
npm install --save-dev @playwright/test
npx playwright install
// tests/e2e/home.spec.ts
import { test, expect } from '@playwright/test';

test('homepage loads correctly', async ({ page }) => {
  await page.goto('/');

  await expect(page).toHaveTitle(/My App/);
  await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});

test('navigation works', async ({ page }) => {
  await page.goto('/');

  await page.getByRole('link', { name: 'About' }).click();
  await expect(page).toHaveURL('/about');
  await expect(page.getByRole('heading', { name: 'About' })).toBeVisible();
});

Solución de problemas

Cuestiones comunes

Hidration Mismatch

// Fix hydration issues
import { useEffect, useState } from 'react';

export default function ClientOnlyComponent() {
  const [hasMounted, setHasMounted] = useState(false);

  useEffect(() => {
    setHasMounted(true);
  }, []);

  if (!hasMounted) {
    return null;
  }

  return <div>Client-only content</div>;
}

Memory Leaks

// Proper cleanup
useEffect(() => {
  const subscription = api.subscribe(data => {
    setData(data);
  });

  return () => {
    subscription.unsubscribe();
  };
}, []);
```_

#### Errores de construcción
```bash
# Clear Next.js cache
rm -rf .next

# Clear node_modules
rm -rf node_modules package-lock.json
npm install

# Check for TypeScript errors
npx tsc --noEmit

Cuestiones de ejecución

// Optimize re-renders
import { memo, useMemo, useCallback } from 'react';

const ExpensiveComponent = memo(({ data, onUpdate }) => {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      processed: true
    }));
  }, [data]);

  const handleUpdate = useCallback((id) => {
    onUpdate(id);
  }, [onUpdate]);

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} data={item} onUpdate={handleUpdate} />
      ))}
    </div>
  );
});

Buenas prácticas

Estructura del proyecto

# Recommended structure
src/
├── app/                    # App Router pages
├── components/             # Reusable components
│   ├── ui/                # Basic UI components
│   └── features/          # Feature-specific components
├── lib/                   # Utility functions
├── hooks/                 # Custom hooks
├── types/                 # TypeScript types
├── styles/                # Global styles
└── __tests__/             # Test files

Prácticas óptimas de rendimiento

// Use Server Components by default
export default async function Page() {
  const data = await fetchData();
  return <div>{data.title}</div>;
}

// Use Client Components only when needed
'use client';
export default function InteractiveComponent() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// Optimize images
<Image
  src="/hero.jpg"
  alt="Hero"
  width={800}
  height={600}
  priority
  placeholder="blur"
/>

SEO Buenas prácticas

// Metadata API
export const metadata = {
  title: 'My Page',
  description: 'Page description',
  keywords: ['next.js', 'react', 'javascript'],
  authors: [{ name: 'John Doe' }],
  openGraph: {
    title: 'My Page',
    description: 'Page description',
    images: ['/og-image.jpg'],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'My Page',
    description: 'Page description',
    images: ['/twitter-image.jpg'],
  },
};

// Dynamic metadata
export async function generateMetadata({ params }) {
  const post = await fetchPost(params.id);

  return {
    title: post.title,
    description: post.excerpt,
  };
}

Prácticas óptimas de seguridad

// Environment variables
const apiKey = process.env.API_KEY; // Server-side only
const publicApiUrl = process.env.NEXT_PUBLIC_API_URL; // Client-side accessible

// Content Security Policy
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline';"
  }
];

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: securityHeaders,
      },
    ];
  },
};

-...

Resumen

Next.js es un poderoso marco de reacción que proporciona una solución completa para la construcción de aplicaciones web modernas. Las características principales incluyen:

  • App Router: Nuevo sistema de enrutamiento con diseños, estados de carga y límites de errores
  • Server Components: Render componentes en el servidor para un mejor rendimiento
  • ** Rutas de la API**: Puntos finales incorporados de la API con capacidades de personal completo
  • ** Optimización de imagen**: Optimización de imagen automática y carga perezosa
  • Performance: Optimizaciones incorporadas para los Vitales Web Core
  • Deployment: Despliegue inigualable a Vercel y otras plataformas

Para obtener resultados óptimos, apalanque los componentes del servidor por defecto, utilice los componentes del cliente con moderación, implemente estrategias de caché adecuadas, y siga las convenciones Next.js para el enrutamiento y la búsqueda de datos.

" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }

función generaPDF() { ventana.print(); } ■/script título