Next.js 16: Täydellinen opas uusiin ominaisuuksiin ja siirtymään

Kattava opas Next.js 16:n uusiin ominaisuuksiin: use cache -direktiivi, Turbopack, Partial Prerendering (PPR), proxy.ts, React Compiler ja DevTools MCP. Sisältää käytännön esimerkkejä ja vaiheittaisen siirtymäoppaan.

Johdanto

Next.js 16 on rehellisesti sanottuna yksi isoimmista julkaisuista Vercelin React-frameworkin historiassa. Tämä versio tuo mukanaan perustavanlaatuisia muutoksia siihen, miten kehittäjät rakentavat ja optimoivat web-sovelluksia — ja tällä kertaa muutokset ovat oikeasti merkittäviä.

Suurimmat uudistukset koskevat välimuistitoimintoja, paketointityökaluja ja kehitysympäristön nopeutta. Next.js 15:n implisiittinen välimuistimalli (joka aiheutti monelle harmaita hiuksia) on korvattu eksplisiittisellä opt-in-mallilla. Turbopack on nyt oletuspaketointityökalu devissä, ja nopeusero on ihan konkreettisesti huomattava.

Middleware on nimetty uudelleen proxy.ts-tiedostoksi. Kuulostaa pieneltä muutokselta, mutta se kuvastaa paremmin sitä, mitä tiedosto oikeasti tekee. Lisäksi mukana tulee React 19.2 -tuki, View Transitions -API ja useEffectEvent-hook.

React Compiler automatisoi memoisoinnin ja poistaa tarpeen manuaalisille useMemo- ja useCallback-kutsuille. Parhaimmillaan tämä tuo jopa 12 prosentin suorituskykyparannuksen.

Käydään tässä oppaassa läpi kaikki keskeiset muutokset käytännön esimerkein ja lopuksi näytetään, miten siirryt Next.js 15:stä turvallisesti eteenpäin.

Cache Components ja "use cache" -direktiivi

Tämä on ehkä se isoin arkkitehtuurimuutos koko julkaisussa. Aikaisemmissa versioissa Next.js välimuisti automaattisesti lähes kaiken — fetch-pyynnöt, komponentit, sivut — mikä johti usein yllättävään käyttäytymiseen. Data ei yksinkertaisesti päivittynyt silloin kun piti. Next.js 16 kääntää lähestymistavan päälaelleen: nyt mikään ei ole välimuistissa oletuksena, ja kehittäjän pitää itse merkitä, mitä haluaa cachettaa.

Oma kokemukseni on, että tämä muutos tekee debuggaamisesta huomattavasti helpompaa.

Peruskäyttö tiedosto- ja funktiotasolla

Uusi "use cache" -direktiivi on keskeinen työkalu välimuistin hallintaan. Sitä voi käyttää sekä tiedostotasolla että yksittäisten funktioiden tasolla. Tiedostotason käyttö välimuistii kaikki kyseisen tiedoston eksportaamat funktiot ja komponentit:

"use cache";

// Tämä koko tiedosto on välimuistissa
export async function getProductData(id: string) {
  const response = await fetch(`https://api.example.com/products/${id}`);
  return response.json();
}

export async function getUserProfile(userId: string) {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  return response.json();
}

// Myös React-komponentit voidaan välimuistia
export default function ProductCard({ id }: { id: string }) {
  const product = await getProductData(id);

  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>{product.price} €</p>
    </div>
  );
}

Funktiotasolla saat hienosyisemmän kontrollin. Tämä on kätevää silloin, kun haluat välimuistia vain osan tiedoston funktioista:

// Ilman tiedostotason "use cache" -direktiiviä

export async function getStaticContent() {
  "use cache";

  // Tämä funktio on välimuistissa
  const response = await fetch("https://api.example.com/static/content");
  return response.json();
}

export async function getDynamicUserData(userId: string) {
  // Tätä funktiota EI välimuistita
  const response = await fetch(`https://api.example.com/users/${userId}/realtime`);
  return response.json();
}

cacheLife-profiilit: hours, days ja weeks

Next.js 16 tarjoaa kolme valmista välimuistiprofilia, jotka määrittävät datan voimassaoloajan. Nämä profiilit tekevät välimuistin hallinnasta intuitiivisempaa — ei tarvitse enää miettiä tarkkoja sekuntimääriä:

"use cache";

import { cacheLife } from "next/cache";

// 'hours' - lyhytaikainen välimuisti (1 tunti, revalidointi 15 min)
export async function getWeatherData() {
  cacheLife("hours");

  const response = await fetch("https://api.weather.com/current");
  return response.json();
}

// 'days' - keskipitkä välimuisti (1 päivä, revalidointi 1 tunti)
export async function getBlogPosts() {
  cacheLife("days");

  const response = await fetch("https://api.example.com/blog/posts");
  return response.json();
}

// 'weeks' - pitkäaikainen välimuisti (1 viikko, revalidointi 1 päivä)
export async function getStaticPages() {
  cacheLife("weeks");

  const response = await fetch("https://api.example.com/pages");
  return response.json();
}

Omien profiilien määrittely onnistuu next.config.ts-tiedostossa:

import type { NextConfig } from "next";

const config: NextConfig = {
  cacheLife: {
    // Määrittele omia profiileja
    custom: {
      stale: 3600, // 1 tunti ennen vanhenemista
      revalidate: 900, // Revalidoi 15 minuutin välein
      expire: 86400, // Poista välimuistista 24 tunnin jälkeen
    },
    // Ylikirjoita olemassa olevia profiileja
    hours: {
      stale: 7200, // 2 tuntia
      revalidate: 1800, // 30 minuuttia
    },
  },
};

export default config;

use cache: private - pyyntökohtainen data

Entä jos haluat välimuistia dataa käyttäjäkohtaisesti? Siihen on "use cache: private". Tämä luo välimuistin, joka on sidottu tiettyyn käyttäjäsessioon tai pyyntöön:

"use cache: private";

import { cookies } from "next/headers";

// Tämä data välimuistitetaan käyttäjäkohtaisesti
export async function getUserDashboard() {
  const sessionCookie = (await cookies()).get("session");

  const response = await fetch("https://api.example.com/dashboard", {
    headers: {
      Authorization: `Bearer ${sessionCookie?.value}`,
    },
  });

  return response.json();
}

// Toinen esimerkki: käyttäjäkohtaiset suositukset
export async function getPersonalizedRecommendations(userId: string) {
  "use cache: private";

  const response = await fetch(
    `https://api.example.com/recommendations/${userId}`
  );

  return response.json();
}

Private-välimuisti on erityisen kätevä tilanteissa, joissa data on käyttäjäkohtaista mutta ei muutu jatkuvasti. Profiilitiedot ja tilaushistoria ovat hyviä esimerkkejä — reaaliaikaiset notifikaatiot tai chat-viestit taas eivät sovellu tähän.

use cache: remote - hajautettu välimuisti

Suuremmissa sovelluksissa, joissa pyörii useita palvelimia tai serverless-funktioita, välimuistin jakaminen instanssien välillä on välttämätöntä. "use cache: remote" mahdollistaa hajautetun cachen esimerkiksi Redisin kanssa:

"use cache: remote";

import { cacheLife } from "next/cache";

// Tämä data jaetaan kaikkien palvelininstanssien välillä
export async function getGlobalStatistics() {
  cacheLife("hours");

  const response = await fetch("https://api.example.com/stats/global");
  return response.json();
}

// Esimerkki: tuoteluettelon välimuisti
export async function getProductCatalog() {
  "use cache: remote";
  cacheLife("days");

  const response = await fetch("https://api.example.com/products/catalog");
  const data = await response.json();

  return {
    products: data.products,
    categories: data.categories,
    lastUpdated: new Date().toISOString(),
  };
}

Konfigurointi next.config.ts-tiedostossa

Uusien välimuistitoimintojen käyttöön tarvitaan cacheComponents-asetus next.config.ts-tiedostossa:

import type { NextConfig } from "next";

const config: NextConfig = {
  experimental: {
    // Aktivoi uusi välimuistimalli
    cacheComponents: true,
  },
};

export default config;

Turbopack vakiopaketointityökaluna

Turbopack on nyt Next.js 16:n oletuspaketointityökalu kehitysympäristössä. Tämä on iso juttu. Kyseessä on Rustilla kirjoitettu, todella nopea bundleri, joka on suunniteltu korvaamaan Webpack modernissa web-kehityksessä.

Suorituskykyparannukset

Turbopackin tuomat parannukset ovat konkreettisia. Next.js-tiimi raportoi seuraavia lukuja todellisista sovelluksista:

  • Fast Refresh 5–10x nopeampi: Koodimuutosten tulokset näkyvät selaimessa lähes välittömästi. Webpackilla suurissa sovelluksissa saattoi mennä 2–5 sekuntia — Turbopackilla alle sekunti.
  • Alkukäynnistys 2–5x nopeampi: Ensimmäinen dev-palvelimen käynnistys on huomattavasti nopeampi. Projekteissa, joissa on tuhansia moduuleja, ero voi olla jopa minuutteja.
  • Tuotantobuildit 50%+ nopeammat: Vaikka Turbopack on optimoitu ennen kaikkea deviin, myös tuotantobuildit hyötyvät selvästi. Build-aika voi pudota 10 minuutista 4–5 minuuttiin.

Tekniset parannukset

Turbopackin nopeus ei ole sattumaa — se perustuu useisiin teknisiin innovaatioihin:

// Next.js 16 käyttää Turbopackia automaattisesti
// Ei tarvitse tehdä mitään erityistä konfiguraatiota

// package.json - skriptit toimivat normaalisti
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

Lazy compilation: Turbopack kääntää vain ne tiedostot, joita oikeasti tarvitaan. Jos sovelluksessasi on 1000 reittiä, mutta avaat vain yhden, kääntöön menee vain se yksi reitti riippuvuuksineen. Tämä tekee käynnistyksestä todella nopeaa.

Inkrementaalinen kääntäminen: Kun muutat yhden tiedoston, vain se ja sen suorat riippuvuudet käännetään uudelleen. Webpack saattoi joutua käsittelemään huomattavasti enemmän.

Rinnakkainen prosessointi: Turbopack hyödyntää nykyaikaisten prosessorien moniydinrakennetta täysimääräisesti. Rustin ansiosta rinnakkainen suoritus on turvallista ilman JavaScriptin tyypillisiä rajoituksia.

Yhteensopivuus ja siirtymä

Hyvä uutinen: Turbopack on suunniteltu Webpack-yhteensopivaksi. Useimmat loaderit ja pluginit toimivat sellaisenaan tai pienillä muutoksilla:

// next.config.ts - Turbopack-konfiguraatio
import type { NextConfig } from "next";

const config: NextConfig = {
  experimental: {
    turbo: {
      // Määrittele mukautetut loaderit tarvittaessa
      rules: {
        "*.svg": {
          loaders: ["@svgr/webpack"],
          as: "*.js",
        },
      },
      // Määrittele resolve-alias
      resolveAlias: {
        "@components": "./components",
        "@utils": "./lib/utils",
      },
    },
  },
};

export default config;

Partial Prerendering (PPR)

Partial Prerendering on mielestäni Next.js 16:n kiinnostavin ominaisuus rendering-puolella. Se yhdistää staattiset ja dynaamiset sisällöt samalle sivulle tavalla, joka oli ennen joko mahdotonta tai vaati hankalia kiertoteitä.

Mikä on PPR ja miksi sillä on väliä?

Perinteisesti Next.js-sivu oli joko kokonaan staattinen (SSG) tai kokonaan dynaaminen (SSR). Yksikin dynaaminen elementti pakotti koko sivun palvelinpuolen renderöintiin. PPR ratkaisee tämän: staattiset osat generoidaan build-aikana ja dynaamiset osat renderöidään vasta pyynnön yhteydessä.

// app/products/[id]/page.tsx
import { Suspense } from "react";
import { getProductDetails } from "@/lib/data";

// Staattinen tuotekuvaus
async function ProductInfo({ id }: { id: string }) {
  const product = await getProductDetails(id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <img src={product.image} alt={product.name} />
    </div>
  );
}

// Dynaaminen saatavuustieto
async function ProductAvailability({ id }: { id: string }) {
  const availability = await fetch(
    `https://api.example.com/inventory/${id}`,
    { cache: "no-store" }
  );
  const data = await availability.json();

  return (
    <div>
      <p>Varastossa: {data.inStock} kpl</p>
      <p>Saapuu: {data.nextDelivery}</p>
    </div>
  );
}

// Pääkomponentti käyttää PPR:ää
export default function ProductPage({ params }: { params: { id: string } }) {
  return (
    <div>
      {/* Staattinen osa renderöidään build-aikana */}
      <ProductInfo id={params.id} />

      {/* Dynaaminen osa renderöidään pyynnön yhteydessä */}
      <Suspense fallback={<div>Ladataan saatavuustietoja...</div>}>
        <ProductAvailability id={params.id} />
      </Suspense>
    </div>
  );
}

Suspense-rajat ja streaming

PPR hyödyntää Reactin Suspense-rajoja dynaamisten osien tunnistamiseen. Kun käyttäjä pyytää sivua, Next.js lähettää heti staattisen HTML:n ja streamaa dynaamiset osat perässä sitä mukaa kuin ne valmistuvat:

// app/dashboard/page.tsx
import { Suspense } from "react";

// Staattinen navigaatio
function DashboardNav() {
  return (
    <nav>
      <a href="/dashboard">Yhteenveto</a>
      <a href="/dashboard/analytics">Analytiikka</a>
      <a href="/dashboard/settings">Asetukset</a>
    </nav>
  );
}

// Dynaaminen käyttäjätilasto
async function UserStats({ userId }: { userId: string }) {
  const stats = await fetch(
    `https://api.example.com/users/${userId}/stats`,
    { cache: "no-store" }
  );
  const data = await stats.json();

  return (
    <div className="stats-grid">
      <div className="stat-card">
        <h3>Näyttökerrat</h3>
        <p>{data.views}</p>
      </div>
      <div className="stat-card">
        <h3>Klikkaukset</h3>
        <p>{data.clicks}</p>
      </div>
    </div>
  );
}

export default function DashboardPage({
  params
}: {
  params: { userId: string }
}) {
  return (
    <div className="dashboard">
      <DashboardNav />
      <Suspense fallback={<div>Ladataan tilastoja...</div>}>
        <UserStats userId={params.userId} />
      </Suspense>
    </div>
  );
}

Asteittainen käyttöönotto

PPR on vielä kokeellinen ominaisuus Next.js 16:ssa, ja se pitää aktivoida erikseen. Voit ottaa sen käyttöön koko sovelluksessa tai testata ensin yksittäisillä reiteillä (suosittelen jälkimmäistä):

// next.config.ts - Globaali PPR-aktivointi
import type { NextConfig } from "next";

const config: NextConfig = {
  experimental: {
    ppr: true,
  },
};

export default config;

Reittikohtainen aktivointi onnistuu route segment -konfiguraatiolla:

// app/products/[id]/page.tsx
export const experimental_ppr = true;

export default function ProductPage({ params }: { params: { id: string } }) {
  // Sivun sisältö...
}

proxy.ts korvaa middlewaren

Next.js 16:ssa middleware.ts on nyt proxy.ts. Pelkkä uudelleennimeäminen? Ei ihan. Muutos heijastaa paremmin sitä, mitä tiedosto oikeasti tekee Next.js-sovelluksissa.

Miksi muutos tehtiin?

Termi "middleware" viittaa perinteisesti ohjelmistoon, joka toimii välittäjänä eri kerrosten välillä. Express.js:ssä middleware tarkoittaa funktioita, jotka käsittelevät pyyntöjä ennen reitinkäsittelijöitä.

Next.js:n "middleware" toimi kuitenkin käytännössä reverse proxyna. Se ohjasi pyyntöjä, muokkasi otsikoita, teki auth-tarkistuksia ja päätti, käsitelläänkö pyyntö vai ei. Nimi "proxy" kuvaa tätä paremmin.

Siirtyminen middlewaresta proxyyn

Siirtymään löytyy automaattinen codemod-työkalu:

# Suorita codemod-työkalu
npx @next/codemod@latest middleware-to-proxy ./

Joitakin muutoksia pitää kuitenkin tehdä käsin:

// ENNEN: middleware.ts (Next.js 15)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token");

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

  const response = NextResponse.next();
  response.headers.set("x-custom-header", "value");

  return response;
}

export const config = {
  matcher: ["/dashboard/:path*"],
};
// JÄLKEEN: proxy.ts (Next.js 16)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function proxy(request: NextRequest) {
  const token = request.cookies.get("auth-token");

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

  const response = NextResponse.next();
  response.headers.set("x-custom-header", "value");

  return response;
}

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

Tärkeimmät erot

Suoritusympäristö: Isoin tekninen muutos on se, että proxy.ts suoritetaan Node.js-runtimessa Edge-runtimen sijaan. Tämä tarkoittaa pääsyä kaikkiin Node.js:n API:hin ja kirjastoihin, mikä oli aiemmin rajoitettua:

// proxy.ts - Node.js-ympäristössä
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import crypto from "crypto"; // Node.js crypto-moduuli toimii nyt

export function proxy(request: NextRequest) {
  const hash = crypto
    .createHash("sha256")
    .update(request.url)
    .digest("hex");

  console.log("Request hash:", hash);

  return NextResponse.next();
}

Konfiguraatio-optioiden muutokset: Jotkin next.config.ts-asetukset on nimetty uudelleen:

// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
  // VANHA: skipMiddlewareUrlNormalize
  // UUSI: skipProxyUrlNormalize
  skipProxyUrlNormalize: true,
};

export default config;

Käytännön esimerkki

Tässä on kattavampi esimerkki proxy.ts-tiedostosta, joka hoitaa autentikoinnin, lokalisaation ja turvallisuusotsikot kerralla:

// proxy.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 1. Lokalisaatio - ohjaa oletuskielelle
  if (pathname === "/") {
    const locale = request.cookies.get("NEXT_LOCALE")?.value || "fi";
    return NextResponse.redirect(new URL(`/${locale}`, request.url));
  }

  // 2. Autentikointi - suojaa admin-reitit
  if (pathname.startsWith("/admin")) {
    const session = request.cookies.get("session");

    if (!session) {
      const loginUrl = new URL("/login", request.url);
      loginUrl.searchParams.set("redirect", pathname);
      return NextResponse.redirect(loginUrl);
    }
  }

  // 3. Security headers
  const response = NextResponse.next();
  response.headers.set("x-frame-options", "DENY");
  response.headers.set("x-content-type-options", "nosniff");
  response.headers.set("referrer-policy", "origin-when-cross-origin");

  return response;
}

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

React 19.2 ja uudet ominaisuudet

Next.js 16 tuo mukanaan React 19.2:n, jossa on useita uusia ominaisuuksia kehittäjäkokemuksen parantamiseksi. Käydään tärkeimmät läpi.

View Transitions - sujuvat siirtymäanimaatiot

View Transitions API mahdollistaa siistit siirtymäanimaatiot sivujen välillä ilman erillisiä animaatiokirjastoja. Aiemmin tämä onnistui vain SPA-sovelluksissa, mutta nyt se toimii myös server-komponenttien kanssa:

// app/components/NavigationLink.tsx
"use client";

import { useRouter } from "next/navigation";
import { startTransition } from "react";

export function NavigationLink({
  href,
  children
}: {
  href: string;
  children: React.ReactNode;
}) {
  const router = useRouter();

  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault();

    // Tarkista, tukeeko selain View Transitions API:a
    if (document.startViewTransition) {
      document.startViewTransition(() => {
        startTransition(() => {
          router.push(href);
        });
      });
    } else {
      router.push(href);
    }
  };

  return (
    <a href={href} onClick={handleClick}>
      {children}
    </a>
  );
}

View Transitionsilla voi tehdä myös elementtikohtaisia animaatioita CSS:n avulla — voit nimetä siirtyviä elementtejä ja luoda niihin kohdistettuja efektejä.

useEffectEvent - ei-reaktiivinen logiikka

Jos olet koskaan kamppaillut useEffectin riippuvuuksien kanssa, tämä hook on sinua varten. useEffectEvent ratkaisee klassisen ongelman: miten käyttää tilaa tai propseja effect-funktiossa ilman tarpeetonta uudelleensuorittamista:

"use client";

import { useState, useEffect, useEffectEvent } from "react";

export function ChatRoom({ roomId }: { roomId: string }) {
  const [messages, setMessages] = useState<string[]>([]);
  const [theme, setTheme] = useState<"light" | "dark">("light");

  // useEffectEvent luo ei-reaktiivisen event handlerin
  const onMessage = useEffectEvent((message: string) => {
    // Tämä funktio voi lukea tuoreimman theme-tilan
    // ilman että effect täytyy suorittaa uudelleen
    console.log(`Uusi viesti ${theme} -teemassa:`, message);
    setMessages((prev) => [...prev, message]);
  });

  useEffect(() => {
    const connection = connectToChat(roomId);
    connection.on("message", onMessage);

    return () => {
      connection.disconnect();
    };
    // Effect riippuu vain roomId:stä, ei theme:sta
  }, [roomId]);

  return (
    <div className={`chat-room theme-${theme}`}>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
        Vaihda teemaa
      </button>
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className="message">{msg}</div>
        ))}
      </div>
    </div>
  );
}

Activity - taustalla renderöinti

Activity API on mielenkiintoinen uutuus. Se mahdollistaa komponenttien renderöinnin taustalla piilottamalla UI:n display: none -tyylillä, mutta säilyttäen tilan ja siivoamalla Effectit. Käytännössä tämä on kätevää, kun haluat esikäsitellä sisältöä tai säilyttää tilan vaikka komponentti ei ole näkyvissä.

React Compiler

Tämä on ehdottomasti yksi Next.js 16:n kuumimmista ominaisuuksista. React Compiler analysoi komponenttisi ja optimoi ne automaattisesti — ilman yhtäkään manuaalista useMemo-, useCallback- tai React.memo-kutsua.

Aktivointi next.config.ts-tiedostossa

Käyttöönotto on ilahduttavan yksinkertaista:

// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
  experimental: {
    reactCompiler: true,
  },
};

export default config;

Ennen ja jälkeen

Katsotaan konkreettisesti, miten React Compiler muuttaa arkipäiväistä koodausta:

// ENNEN: Manuaaliset optimoinnit Next.js 15:ssä
"use client";

import { useState, useMemo, useCallback } from "react";

export function ProductList({ products }: { products: Product[] }) {
  const [searchTerm, setSearchTerm] = useState("");
  const [selectedCategory, setSelectedCategory] = useState("all");

  // Manuaalinen memoisointi suodatetuille tuotteille
  const filteredProducts = useMemo(() => {
    return products.filter((product) => {
      const matchesSearch = product.name
        .toLowerCase()
        .includes(searchTerm.toLowerCase());
      const matchesCategory =
        selectedCategory === "all" ||
        product.category === selectedCategory;
      return matchesSearch && matchesCategory;
    });
  }, [products, searchTerm, selectedCategory]);

  // Manuaalinen memoisointi event handlereille
  const handleSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setSearchTerm(e.target.value);
  }, []);

  return (
    <div>
      <input type="text" value={searchTerm} onChange={handleSearch} />
      {filteredProducts.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}
// JÄLKEEN: React Compiler optimoi automaattisesti Next.js 16:ssa
"use client";

import { useState } from "react";

export function ProductList({ products }: { products: Product[] }) {
  const [searchTerm, setSearchTerm] = useState("");
  const [selectedCategory, setSelectedCategory] = useState("all");

  // Ei tarvetta useMemo:lle - Compiler optimoi automaattisesti
  const filteredProducts = products.filter((product) => {
    const matchesSearch = product.name
      .toLowerCase()
      .includes(searchTerm.toLowerCase());
    const matchesCategory =
      selectedCategory === "all" ||
      product.category === selectedCategory;
    return matchesSearch && matchesCategory;
  });

  // Ei tarvetta useCallback:lle - Compiler hoitaa
  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    setSearchTerm(e.target.value);
  };

  return (
    <div>
      <input type="text" value={searchTerm} onChange={handleSearch} />
      {filteredProducts.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Huomaatko eron? Koodi on puhtaampaa ja luettavampaa, mutta suorituskyky on sama — tai jopa parempi.

Suorituskykyparannukset

React Compilerin konkreettiset hyödyt:

  • Jopa 12 % nopeampi renderöinti: Automaattinen optimointi tekee komponenteista tehokkaampia
  • Vähemmän turhia uudelleenrenderöintejä: Compiler ymmärtää tarkasti, mitkä komponentit oikeasti tarvitsevat päivitystä
  • Pienempi bundle-koko: Ilman useMemo/useCallback-wrappereita lopullinen bundle on kevyempi
  • Nolla runtime-kustannusta: Optimoinnit tapahtuvat build-aikana, eli ajonaikaiseen suorituskykyyn ei tule lisäkuormaa

DevTools MCP

Next.js 16 esittelee DevTools MCP:n (Model Context Protocol), joka on uusi tapa integroida tekoäly kehitystyökaluihin. Käytännössä tämä tarkoittaa, että AI-assistentit pääsevät käsiksi sovelluksesi tilaan, lokeihin ja verkkoliikenteeseen — mikä tekee debuggaamisesta tehokkaampaa.

AI ymmärtää Next.js:n reitityksen, välimuistisemantiikat ja renderöintikäyttäytymisen, joten se osaa auttaa ongelmien tunnistamisessa ja ratkaisemisessa paljon perinteisiä työkaluja nopeammin. Tämä on selvästi suunta, johon koko alalla ollaan menossa.

Siirtyminen Next.js 16:een

Next.js 15:stä 16:een siirtyminen on suhteellisen suoraviivaista, mutta vaatii muutamia tärkeitä askeleita. Tässä vaiheittainen opas.

Vaihe 1: Päivitä riippuvuudet

Aloitetaan perusteista — päivitä Next.js ja React:

# Käyttäen npm:ää
npm install next@latest react@latest react-dom@latest

# Päivitä myös TypeScript-tyypit
npm install -D @types/react@latest @types/react-dom@latest

Vaihe 2: Suorita codemod-työkalut

Next.js tarjoaa automaattisia työkaluja yleisimpien muutosten hoitamiseen:

# Muunna middleware.ts -> proxy.ts
npx @next/codemod@latest middleware-to-proxy ./

# Päivitä cache-API uuteen syntaksiin
npx @next/codemod@latest app-dir-cache ./

Vaihe 3: Päivitä next.config.ts

Tarkista konfiguraatiotiedostosi ja ota käyttöön uudet asetukset:

// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
  experimental: {
    reactCompiler: true,
    cacheComponents: true,
    ppr: false, // Aloita false:lla, testaa sitten reittikohtaisesti
  },

  // Päivitä vanhat asetukset
  // skipMiddlewareUrlNormalize -> skipProxyUrlNormalize
  skipProxyUrlNormalize: true,
};

export default config;

Vaihe 4: Päivitä cache-strategiat

Tämä on se kohta, joka vaatii eniten manuaalista työtä. Korvaa implisiittiset cache-kutsut "use cache" -direktiivillä:

// ENNEN: app/lib/data.ts (Next.js 15)
export async function getProducts() {
  // Automaattisesti välimuistissa
  const res = await fetch("https://api.example.com/products");
  return res.json();
}

// JÄLKEEN: app/lib/data.ts (Next.js 16)
"use cache";

import { cacheLife } from "next/cache";

export async function getProducts() {
  cacheLife("days"); // Eksplisiittinen välimuisti
  const res = await fetch("https://api.example.com/products");
  return res.json();
}

Vaihe 5: Testaa ja ota käyttöön

Käynnistä dev-palvelin, testaa sovellus ja tee tuotantobuild:

# Käynnistä dev-palvelin
npm run dev

# Tee tuotantobuild
npm run build

# Testaa tuotantoversio paikallisesti
npm run start

Yleiset ongelmat ja ratkaisut

Ongelma: React Compiler aiheuttaa runtime-virheitä

// Ratkaisu: Poista compiler käytöstä ongelmakomponentissa
"use no memo";

export function ProblematicComponent() {
  // Tämä komponentti ei käytä React Compileria
}

Ongelma: TypeScript-virheet proxy-muutoksen jälkeen

// Ratkaisu: Varmista että funktion nimi on "proxy"
export function proxy(request: NextRequest) {
  // ...
}

Yhteenveto

Next.js 16 on kokonaisuutena merkittävä päivitys. Tiivistettynä tärkeimmät muutokset:

  • Uusi cache-malli: Opt-in-välimuisti "use cache" -direktiivillä antaa kehittäjille täyden hallinnan — ei enää yllätyksiä
  • Turbopack oletuksena: 5–10x nopeampi Fast Refresh ja 2–5x nopeammat buildit tekevät kehittämisestä mukavampaa
  • Partial Prerendering: Staattiset ja dynaamiset sisällöt samalla sivulla — sekä suorituskyky että käyttäjäkokemus paranevat
  • proxy.ts: Middleware uudelleennimettynä vastaamaan todellista toiminnallisuutta
  • React 19.2: View Transitions, useEffectEvent ja Activity tuovat uusia mahdollisuuksia
  • React Compiler: Automaattinen memoisointi yksinkertaistaa koodia ja parantaa suorituskykyä
  • DevTools MCP: AI-avusteinen debuggaus vie kehittäjäkokemuksen uudelle tasolle

Suosittelen aloittamaan siirtymän asteittain. Ota ensin käyttöön React Compiler ja Turbopack, jotka vaativat minimaalista konfiguraatiota. Siirry sitten uuteen cache-malliin, ja testaa PPR:ää ensin yksittäisillä sivuilla. Huolellisella testauksella ja asteittaisella käyttöönotolla koko homma sujuu turvallisesti.

Tietoa Kirjoittajasta Editorial Team

Our team of expert writers and editors.