Migrazione a Next.js 16: Performance, Turbopack e React 19

THEJORD Team11 min di lettura
nextjsreactmigrationperformancetechnical

Come abbiamo migrato THEJORD a Next.js 16: architettura App Router, Turbopack (7x più veloce), React 19 Server Components, best practices e performance improvements ottenuti.

Migrazione a Next.js 16: Performance, Turbopack e React 19

Migrazione a Next.js 16: Performance, Turbopack e React 19

THEJORD.IT è stato costruito con le tecnologie più moderne per garantire performance eccezionali e developer experience ottimale. In questo articolo tecnico raccontiamo il processo di migrazione a Next.js 16, le sfide affrontate, i benefici ottenuti e le lezioni apprese.

Perché Next.js 16

La decisione di adottare Next.js 16 (con React 19 e Turbopack) è stata guidata da obiettivi chiari:

  • Performance: Build time ridotto del 70%, hot reload istantaneo
  • Developer Experience: Turbopack elimina lentezza di Webpack in dev mode
  • React 19 Features: Server Components, Actions, Suspense migliorato
  • SEO: Server-Side Rendering (SSR) e Static Site Generation (SSG) built-in
  • Type Safety: Integrazione TypeScript nativa first-class

Cosa Cambia rispetto a Next.js 14

Feature Next.js 14 Next.js 16
Bundler Webpack (lento) Turbopack (10x più veloce)
React Version React 18 React 19 (Actions, Suspense++)
Build Time ~45s (THEJORD) ~12s (THEJORD)
Hot Reload 1-3s <200ms
Server Actions Experimental Stable
Partial Prerendering ✅ (preview)

Architettura di THEJORD con Next.js 16

App Router (app/) vs Pages Router

THEJORD utilizza la nuova App Router introdotta in Next.js 13 e perfezionata in 16:

thejord-web/
├── app/
│   ├── [locale]/              # i18n routing
│   │   ├── layout.tsx         # Root layout (shared)
│   │   ├── page.tsx           # Homepage
│   │   ├── tools/
│   │   │   ├── diff-checker/
│   │   │   │   └── page.tsx   # Diff Checker tool
│   │   │   ├── hash-generator/
│   │   │   │   └── page.tsx   # Hash Generator tool
│   │   │   └── ...
│   │   ├── blog/
│   │   │   ├── page.tsx       # Blog list (SSG)
│   │   │   └── [slug]/
│   │   │       └── page.tsx   # Blog post (SSG)
│   │   └── admin/
│   │       └── posts/
│   │           └── page.tsx   # Admin panel (CSR)
│   └── api/
│       ├── proxy/             # API proxy to backend
│       └── revalidate/        # ISR revalidation
├── components/                # React components
├── lib/                       # Utilities
└── public/                    # Static assets

Rendering Strategies

THEJORD combina diverse strategie per ottimizzare performance e SEO:

1. Static Site Generation (SSG) - Blog Posts

// app/[locale]/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts()
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)

  return (
    

{post.title}

) } // Metadata SEO generata a build time export async function generateMetadata({ params }: { params: { slug: string } }) { const post = await getPost(params.slug) return { title: post.metaTitle, description: post.metaDescription, } }

Benefici: HTML pre-renderizzato, instant loading, perfect SEO

2. Incremental Static Regeneration (ISR) - Tool Pages

// app/[locale]/tools/diff-checker/page.tsx
export const revalidate = 3600 // Revalidate ogni ora

export default function DiffChecker() {
  return 
}

// API per forzare revalidation
// POST /api/revalidate?path=/tools/diff-checker
export async function POST(request: Request) {
  const { path } = await request.json()
  await revalidate(path)
  return Response.json({ revalidated: true })
}

Benefici: Static speed + dynamic freshness

3. Client-Side Rendering (CSR) - Admin Panel

// app/[locale]/admin/posts/page.tsx
'use client'

import { useState, useEffect } from 'react'

export default function AdminPosts() {
  const [posts, setPosts] = useState([])

  useEffect(() => {
    fetch('/api/proxy/api/posts')
      .then(res => res.json())
      .then(setPosts)
  }, [])

  return 
}

Benefici: Interattività, real-time updates, protected routes

Turbopack: Il Nuovo Bundler

Webpack vs Turbopack: Benchmark Reali

Test eseguiti su THEJORD codebase (45 componenti, 120 file):

Operazione Webpack (Next 14) Turbopack (Next 16) Miglioramento
Cold start 8.2s 1.1s 7.5x più veloce
Hot reload (small change) 2.1s 0.15s 14x più veloce
Production build 44s 12s 3.7x più veloce
Memory usage (dev) 850MB 420MB -50%

Come Abilitare Turbopack

// next.config.mjs
const nextConfig = {
  // Dev mode con Turbopack
  experimental: {
    turbo: {
      rules: {
        '*.svg': {
          loaders: ['@svgr/webpack'],
          as: '*.js',
        },
      },
    },
  },
}

export default nextConfig
# package.json
{
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build",
    "start": "next start"
  }
}

Gotchas e Limitazioni

  • Custom Webpack config: Non completamente supportata, richiede migration
  • Some loaders: Potrebbero non funzionare (es. vecchi Webpack loaders)
  • Source maps: Formato leggermente diverso, alcuni debugger potrebbero avere issue

React 19 Server Components

Quando Usare Server Components

Server Components eseguono sul server, riducono JavaScript inviato al client:

// components/BlogPostList.tsx (Server Component)
async function BlogPostList() {
  // Fetch diretto da database, senza API call client-side
  const posts = await db.posts.findMany({
    where: { published: true },
    orderBy: { publishedAt: 'desc' }
  })

  return (
    
{posts.map(post => ( ))}
) } // Nessun 'use client' → Server Component di default

Benefici:

  • Zero JavaScript client-side per rendering
  • Accesso diretto a database/filesystem
  • Nessun waterfall di API calls
  • Bundle size ridotto

Quando Usare Client Components

// components/DiffChecker.tsx (Client Component)
'use client'

import { useState } from 'react'
import { diffLines } from 'diff'

export function DiffChecker() {
  const [text1, setText1] = useState('')
  const [text2, setText2] = useState('')
  const [diff, setDiff] = useState([])

  const handleCompare = () => {
    const result = diffLines(text1, text2)
    setDiff(result)
  }

  return (
    // UI interattiva con state, events, browser APIs
  )
}

Usa Client Components per:

  • State (useState, useReducer)
  • Effects (useEffect, custom hooks)
  • Event handlers (onClick, onChange)
  • Browser APIs (localStorage, Web Workers)
  • Third-party interactive libraries

Composition Pattern

// app/[locale]/tools/diff-checker/page.tsx (Server Component)
import { DiffChecker } from '@/components/DiffChecker'
import { ToolMetadata } from '@/components/ToolMetadata'

export default async function DiffCheckerPage() {
  // Fetch metadata sul server
  const metadata = await getToolMetadata('diff-checker')

  return (
    
{/* Server Component */} {/* Client Component passato come children */}
) }

React 19 Server Actions

Server Actions permettono di chiamare funzioni server da client senza creare API route:

// app/actions.ts
'use server'

import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  const post = await db.posts.create({
    data: { title, content, published: false }
  })

  revalidatePath('/admin/posts')
  return { success: true, postId: post.id }
}

export async function publishPost(postId: string) {
  await db.posts.update({
    where: { id: postId },
    data: { published: true, publishedAt: new Date() }
  })

  revalidatePath('/blog')
  return { success: true }
}
// components/CreatePostForm.tsx
'use client'

import { createPost } from '@/app/actions'
import { useFormStatus } from 'react-dom'

function SubmitButton() {
  const { pending } = useFormStatus()
  return (
    
  )
}

export function CreatePostForm() {
  return (