THEJORD LogoTHEJORD

Migration to Next.js 16: Performance, Turbopack, and React 19

THEJORD Team••11 min read
nextjsreactmigrationperformancetechnical

How we migrated THEJORD to Next.js 16: App Router architecture, Turbopack (7x faster), React 19 Server Components, best practices and performance improvements achieved.

Migration to Next.js 16: Performance, Turbopack, and React 19

Migration to Next.js 16: Performance, Turbopack, and React 19

THEJORD.IT was built with the most modern technologies to ensure exceptional performance and optimal developer experience. In this technical article, we share the migration process to Next.js 16, the challenges faced, benefits achieved, and lessons learned.

Why Next.js 16

The decision to adopt Next.js 16 (with React 19 and Turbopack) was driven by clear objectives:

  • Performance: 70% reduced build time, instant hot reload
  • Developer Experience: Turbopack eliminates Webpack slowness in dev mode
  • React 19 Features: Server Components, Actions, improved Suspense
  • SEO: Built-in Server-Side Rendering (SSR) and Static Site Generation (SSG)
  • Type Safety: First-class native TypeScript integration

Changes from Next.js 14

Feature Next.js 14 Next.js 16
Bundler Webpack (slow) Turbopack (10x faster)
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)

THEJORD Architecture with Next.js 16

App Router (app/) vs Pages Router

THEJORD uses the new App Router introduced in Next.js 13 and perfected 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 combines different strategies to optimize performance and 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}

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

Benefits: Pre-rendered HTML, instant loading, perfect SEO

2. Incremental Static Regeneration (ISR) - Tool Pages

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

export default function DiffChecker() {
  return 
}

// API to force 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 })
}

Benefits: 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 
}

Benefits: Interactivity, real-time updates, protected routes

Turbopack: The New Bundler

Webpack vs Turbopack: Real Benchmarks

Tests performed on THEJORD codebase (45 components, 120 files):

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

How to Enable Turbopack

// next.config.mjs
const nextConfig = {
  // Dev mode with 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 and Limitations

  • Custom Webpack config: Not fully supported, requires migration
  • Some loaders: May not work (e.g., old Webpack loaders)
  • Source maps: Slightly different format, some debuggers might have issues

React 19 Server Components

When to Use Server Components

Server Components execute on the server, reducing JavaScript sent to client:

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

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

Benefits:

  • Zero client-side JavaScript for rendering
  • Direct access to database/filesystem
  • No waterfall of API calls
  • Reduced bundle size

When to Use 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 (
    // Interactive UI with state, events, browser APIs
  )
}

Use Client Components for:

  • 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 on server
  const metadata = await getToolMetadata('diff-checker')

  return (
    
{/* Server Component */} {/* Client Component passed as children */}
) }

React 19 Server Actions

Server Actions allow calling server functions from client without creating API routes:

// 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 (