Skip to content
Back to skills

2750 Server Components 249b33ee

ASecurity

Server Components eseguono sul server: - Zero JavaScript bundle size - Accesso diretto a database/API - Riduzione del tempo di hydration - Accesso a risorse server-side

  • 9 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 31, 2026
developmentjavascripttypescriptjavareactnextjsnodeapidatabase

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 31, 2026

npx -y skills add tools-only/X-Skills --skill 2750-server-components_249b33ee --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2750 Server Components 249b33ee?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2750 Server Components 249b33ee
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2750-server-components-249b33ee/badge)](https://www.skillsdirectory.com/skills/tools-only-2750-server-components-249b33ee)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Server Components - Best Practices

## Overview

Server Components eseguono sul server:
- Zero JavaScript bundle size
- Accesso diretto a database/API
- Riduzione del tempo di hydration
- Accesso a risorse server-side

---

## Pattern Base

### Server Component Puro

```typescript
// BEFORE (Client Component)
'use client'
import { useEffect, useState } from 'react'

export default function ProductList() {
  const [products, setProducts] = useState([])

  useEffect(() => {
    fetch('/api/products')
      .then(r => r.json())
      .then(setProducts)
  }, [])

  return (
    <ul>
      {products.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  )
}

// AFTER (Server Component)
import { db } from '@/lib/db'

export default async function ProductList() {
  const products = await db.product.findMany()

  return (
    <ul>
      {products.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  )
}
```

### Client Component Ibrido

```typescript
// ProductCard.tsx - Server Component
import { AddToCartButton } from './AddToCartButton'
import { db } from '@/lib/db'

export async function ProductCard({ id }: { id: string }) {
  const product = await db.product.findById(id)

  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      {/* Solo il bottone è client component */}
      <AddToCartButton productId={id} />
    </div>
  )
}

// AddToCartButton.tsx - Client Component
'use client'

import { useState } from 'react'

export function AddToCartButton({ productId }: { productId: string }) {
  const [adding, setAdding] = useState(false)

  const addToCart = async () => {
    setAdding(true)
    await fetch('/api/cart', {
      method: 'POST',
      body: JSON.stringify({ productId }),
    })
    setAdding(false)
  }

  return (
    <button onClick={addToCart} disabled={adding}>
      {adding ? 'Adding...' : 'Add to Cart'}
    </button>
  )
}
```

---

## React 19 + Next.js 16 Patterns

### Async Params (Next.js 15+)

```typescript
// BEFORE (Next.js 14)
export default async function Page({ params }: { params: { slug: string } }) {
  const post = await fetchPost(params.slug)
  // ...
}

// AFTER (Next.js 15+) - params è una Promise
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await fetchPost(slug)
  // ...
}
```

### use() Hook (React 19)

```typescript
'use client'

import { use } from 'react'

// Promise creata fuori dal componente
const messagePromise = fetchMessage()

export function Message() {
  // Suspende finché la promise non resolve
  const message = use(messagePromise)

  return <p>{message}</p>
}

// Con props
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  const comments = use(commentsPromise)

  return (
    <ul>
      {comments.map(c => <li key={c.id}>{c.text}</li>)}
    </ul>
  )
}
```

### Server Actions Migliorati

```typescript
// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'

// Action con validazione
export async function updateProfile(prevState: any, formData: FormData) {
  const name = formData.get('name') as string
  const email = formData.get('email') as string

  // Validazione
  if (!name || name.length < 2) {
    return { error: 'Name must be at least 2 characters' }
  }

  try {
    await db.user.update({
      where: { id: session.userId },
      data: { name, email },
    })

    revalidatePath('/profile')
    return { success: true }
  } catch (error) {
    return { error: 'Update failed' }
  }
}

// Componente con useActionState (React 19)
'use client'

import { useActionState } from 'react'
import { updateProfile } from './actions'

export function ProfileForm() {
  const [state, action, pending] = useActionState(updateProfile, null)

  return (
    <form action={action}>
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" />
      <button type="submit" disabled={pending}>
        {pending ? 'Saving...' : 'Save'}
      </button>
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">Saved!</p>}
    </form>
  )
}
```

---

## Conversione Client → Server Component

### Checklist di Conversione

1. **Rimuovere 'use client'**
2. **Spostare data fetching nel componente**
3. **Rimuovere useState/useEffect per data loading**
4. **Spostare interattività in componenti figli**

```typescript
// BEFORE - Client Component
'use client'

import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'

export default function ProductPage({ params }: { params: { id: string } }) {
  const [product, setProduct] = useState(null)
  const [loading, setLoading] = useState(true)
  const router = useRouter()

  useEffect(() => {
    fetch(`/api/products/${params.id}`)
      .then(r => r.json())
      .then(data => {
        setProduct(data)
        setLoading(false)
      })
  }, [params.id])

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

  return (
    <div>
      <h1>{product.name}</h1>
      <button onClick={() => router.push('/products')}>Back</button>
    </div>
  )
}

// AFTER - Server Component + Client Component

// ProductPage.tsx (Server Component)
import { db } from '@/lib/db'
import { BackButton } from './BackButton'

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const product = await db.product.findById(id)

  if (!product) {
    notFound()
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <BackButton /> {/* Client component per interattività */}
    </div>
  )
}

// BackButton.tsx (Client Component)
'use client'

import { useRouter } from 'next/navigation'

export function BackButton() {
  const router = useRouter()
  return <button onClick={() => router.back()}>Back</button>
}
```

---

## Accesso a Risorse Server-side

### Database Access

```typescript
// lib/db.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const db = globalForPrisma.prisma || new PrismaClient()

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

// Uso nel componente
import { db } from '@/lib/db'

export default async function Page() {
  // Query diretta - nessuna API route necessaria
  const users = await db.user.findMany()
  // ...
}
```

### File System

```typescript
import { readFile } from 'fs/promises'
import path from 'path'

export default async function Page() {
  const filePath = path.join(process.cwd(), 'content', 'about.md')
  const content = await readFile(filePath, 'utf-8')

  return <Markdown content={content} />
}
```

### Environment Variables

```typescript
// Server Components possono accedere a tutte le env vars
export default async function Page() {
  // ✅ Accesso diretto alle variabili server
  const apiKey = process.env.API_SECRET_KEY
  const dbUrl = process.env.DATABASE_URL

  const data = await fetch('https://api.example.com/data', {
    headers: { Authorization: `Bearer ${apiKey}` },
  })

  // ...
}
```

---

## Errori Comuni

```typescript
// ❌ NON: Usare browser APIs in Server Component
export default function Page() {
  const width = window.innerWidth // ❌ window non esiste sul server
  localStorage.getItem('key')     // ❌ localStorage non esiste sul server
}

// ✅ SÌ: Spostare in Client Component
'use client'
export function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth)
  // ...
}

// ❌ NON: Usare hooks in Server Component
export default function Page() {
  const [count, setCount] = useState(0) // ❌ Hook non funzionano
  useEffect(() => {...}, [])            // ❌
}

// ❌ NON: Dimenticare di gestire errori
export default async function Page() {
  const data = await fetchData() // Può lanciare
  // ...
}

// ✅ SÌ: Gestire errori
export default async function Page() {
  try {
    const data = await fetchData()
    return <DataView data={data} />
  } catch (error) {
    return <ErrorMessage />
  }
}

// ❌ NON: Troppi Client Components annidati
// ServerComponent
//   → ClientComponent1
//     → ClientComponent2
//       → ClientComponent3

// ✅ SÌ: Mantenere Client Components il più in basso possibile
// ServerComponent
//   → ServerComponent
//     → ServerComponent
//       → ClientComponent (solo dove necessario)
```

Files in this skill

  • README.md1.1 KB
  • skill.md8.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.