Loading blog posts...
Loading blog posts...
Laden...
Ontdek de revolutionaire features van Next.js 15, waaronder React 19 support, Turbopack, het nieuwe Form component, verbeterde TypeScript ondersteuning en async request API's. Leer hoe je razendsnel webapplicaties bouwt met de nieuwste versie.
Next.js heeft een revolutie teweeggebracht in de manier waarop we React applicaties bouwen, en met de release van Next.js 15 (en de nieuwste 15.5), bereikt het framework nieuwe hoogten van performance, developer experience en functionaliteit. Of je nu een simpele blog bouwt of een complexe enterprise applicatie, Next.js 15 biedt alles wat je nodig hebt out of the box.
In deze uitgebreide gids verkennen we alles van installatie tot geavanceerde features, inclusief hoe je Markdown rendering kunt inzetten voor blogposts zoals deze!
Next.js 15, uitgebracht in oktober 2024 en continu bijgewerkt gedurende 2025, brengt baanbrekende verbeteringen die het de krachtigste versie tot nu toe maken.
Next.js 15.1 introduceerde stabiele support voor React 19, waarmee cutting-edge React features naar je applicaties komen:
jsx// React 19 features werken nu naadloos in Next.js 15 import { use } from 'react'; export default function UserProfile({ userPromise }) { // Nieuwe 'use' hook voor het afhandelen van promises const user = use(userPromise); return ( <div className="profile"> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); }
Turbopack, de Rust-gebaseerde opvolger van Webpack, levert ongelooflijke performance verbeteringen:
bash# Schakel Turbopack in voor development next dev --turbo # Met Turbopack zie je snelheden zoals: # ✓ Klaar in 1.2s (voorheen 5s) # ✓ Fast Refresh in 50ms (voorheen 1s)
Benchmark Resultaten (op Vercel app):
Traditionele Webpack:
├── Initiële compile: 5.2s
├── Fast Refresh: 980ms
└── Bundle tijd: 12.4s
Turbopack (75-95% sneller):
├── Initiële compile: 1.3s
├── Fast Refresh: 49ms
└── Bundle tijd: 3.1s
Het nieuwe <Form> component breidt HTML forms uit met Next.js superkrachten:
tsximport Form from 'next/form'; export default function SearchPage() { return ( <Form action="/search"> {/* Prefetching voor directe navigatie */} <input name="query" placeholder="Zoeken..." /> <button type="submit">Zoek</button> </Form> ); }
Features:
Next.js 15 introduceert async versies van dynamische API's voor betere data fetching:
typescript// Voorheen (Next.js 14) import { cookies } from 'next/headers'; export default function Page() { const cookieStore = cookies(); const theme = cookieStore.get('theme'); return <div>Thema: {theme}</div>; } // Nu (Next.js 15) import { cookies } from 'next/headers'; export default async function Page() { const cookieStore = await cookies(); const theme = cookieStore.get('theme'); return <div>Thema: {theme}</div>; }
Async API's omvatten nu:
cookies() - Lees request cookiesheaders() - Toegang tot request headersparams - Dynamische route parameterssearchParams - URL search parametersDe nieuwe onRequestError hook vangt fouten op en stuurt ze naar je observability provider:
typescript// instrumentation.ts export async function onRequestError( err: Error, request: Request, context: { routerKind: 'Pages Router' | 'App Router'; routePath: string; routeType: 'render' | 'route' | 'action' | 'middleware'; } ) { // Verstuur naar je error tracking service await fetch('https://your-error-tracker.com/api/errors', { method: 'POST', body: JSON.stringify({ message: err.message, stack: err.stack, url: request.url, route: context.routePath, type: context.routeType, timestamp: new Date().toISOString(), }), }); }
Next.js 15.5 introduceert stabiele typed routes voor compile-time type veiligheid:
typescript// next.config.ts const config: NextConfig = { experimental: { typedRoutes: true, // Nu stabiel! }, }; export default config;
tsximport Link from 'next/link'; export default function Navigation() { return ( <nav> {/* ✅ TypeScript weet dat deze route bestaat */} <Link href="/blog/nextjs-15">Next.js Gids</Link> {/* ❌ TypeScript error: Route bestaat niet */} <Link href="/non-existent-route">Gebroken Link</Link> {/* ✅ Type-safe dynamische routes */} <Link href={{ pathname: '/blog/[slug]', params: { slug: 'nextjs-15' } }} > Dynamische Route </Link> </nav> ); }
Een nieuw Next.js 15 project aanmaken is eenvoudig:
bash# Maak een nieuwe Next.js app npx create-next-app@latest my-nextjs-app # Je krijgt de volgende vragen: # ✔ Wil je TypeScript gebruiken? Ja # ✔ Wil je ESLint gebruiken? Ja # ✔ Wil je Tailwind CSS gebruiken? Ja # ✔ Wil je een `src/` directory gebruiken? Ja # ✔ Wil je App Router gebruiken? Ja # ✔ Wil je Turbopack gebruiken voor `next dev`? Ja cd my-nextjs-app npm run dev
Als je een bestaand project hebt:
bash# Update Next.js npm install next@latest react@latest react-dom@latest # Controleer op breaking changes npm run build # Schakel Turbopack in # Update package.json: { "scripts": { "dev": "next dev --turbo" } }
Een typisch Next.js 15 project ziet er zo uit:
my-nextjs-app/
├── src/
│ ├── app/ # App Router (aanbevolen)
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home pagina
│ │ ├── globals.css # Globale styles
│ │ ├── blog/
│ │ │ ├── page.tsx # Blog lijst
│ │ │ └── [slug]/
│ │ │ └── page.tsx # Blog post
│ │ └── api/
│ │ └── hello/
│ │ └── route.ts # API endpoint
│ ├── components/ # Herbruikbare components
│ └── lib/ # Utility functies
├── public/ # Statische assets
├── content/ # Markdown bestanden
│ └── blog/
│ └── my-post.md
├── next.config.ts # Next.js configuratie
├── tsconfig.json # TypeScript config
└── package.json
Een van de krachtige features van Next.js 15 is naadloze Markdown rendering. Zo bouw je een blog zoals degene die je nu leest!
bashnpm install gray-matter remark remark-html npm install react-syntax-highlighter @types/react-syntax-highlighter npm install reading-time
Maak markdown bestanden in content/blog/:
markdown--- title: "Mijn Eerste Blog Post" excerpt: "Leer hoe je een blog bouwt met Next.js 15" category: "tutorial" date: "2025-11-22" readTime: "5 min leestijd" tags: ["Next.js", "React", "Markdown"] author: "Jouw Naam" --- # Mijn Eerste Blog Post Dit is de inhoud van mijn blog post geschreven in **Markdown**! ## Code Voorbeeld ```javascript console.log('Hallo, Next.js 15!');
### Stap 3: Maak Markdown Parser
```typescript
// src/lib/markdown.ts
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { remark } from 'remark';
import html from 'remark-html';
const postsDirectory = path.join(process.cwd(), 'content/blog');
export interface BlogPost {
slug: string;
title: string;
excerpt: string;
category: string;
date: string;
readTime: string;
tags: string[];
author: string;
content: string;
}
export async function getPost(slug: string): Promise<BlogPost> {
const fullPath = path.join(postsDirectory, `${slug}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
// Parse markdown frontmatter
const { data, content } = matter(fileContents);
// Converteer markdown naar HTML
const processedContent = await remark()
.use(html)
.process(content);
return {
slug,
content: processedContent.toString(),
...data,
} as BlogPost;
}
export function getAllPosts(): Array<Omit<BlogPost, 'content'>> {
const fileNames = fs.readdirSync(postsDirectory);
const posts = fileNames
.filter(fileName => fileName.endsWith('.md'))
.map(fileName => {
const slug = fileName.replace(/\.md$/, '');
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data } = matter(fileContents);
return {
slug,
...data,
} as Omit<BlogPost, 'content'>;
})
.sort((a, b) => (a.date > b.date ? -1 : 1));
return posts;
}
tsx// src/app/blog/page.tsx import Link from 'next/link'; import { getAllPosts } from '@/lib/markdown'; export default async function BlogPage() { const posts = getAllPosts(); return ( <div className="container mx-auto px-4 py-16"> <h1 className="text-4xl font-bold mb-8">Blog</h1> <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3"> {posts.map((post) => ( <Link key={post.slug} href={`/blog/${post.slug}`} className="group block p-6 bg-white rounded-lg shadow-lg hover:shadow-xl transition-shadow" > <div className="flex gap-2 mb-3"> {post.tags.map((tag) => ( <span key={tag} className="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded" > {tag} </span> ))} </div> <h2 className="text-2xl font-bold mb-2 group-hover:text-blue-600 transition-colors"> {post.title} </h2> <p className="text-gray-600 mb-4">{post.excerpt}</p> <div className="flex justify-between text-sm text-gray-500"> <span>{post.date}</span> <span>{post.readTime}</span> </div> </Link> ))} </div> </div> ); }
tsx// src/app/blog/[slug]/page.tsx import { notFound } from 'next/navigation'; import { getPost, getAllPosts } from '@/lib/markdown'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; export async function generateStaticParams() { const posts = getAllPosts(); return posts.map((post) => ({ slug: post.slug, })); } export default async function BlogPostPage({ params, }: { params: { slug: string }; }) { const post = await getPost(params.slug); if (!post) { notFound(); } return ( <article className="container mx-auto px-4 py-16 max-w-4xl"> {/* Header */} <header className="mb-12"> <div className="flex gap-2 mb-4"> {post.tags.map((tag) => ( <span key={tag} className="text-xs px-3 py-1 bg-blue-100 text-blue-800 rounded-full" > {tag} </span> ))} </div> <h1 className="text-5xl font-bold mb-4">{post.title}</h1> <div className="flex items-center gap-4 text-gray-600"> <span>{post.author}</span> <span>•</span> <span>{post.date}</span> <span>•</span> <span>{post.readTime}</span> </div> </header> {/* Content met Syntax Highlighting */} <div className="prose prose-lg max-w-none"> <ReactMarkdown components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( <SyntaxHighlighter style={vscDarkPlus} language={match[1]} PreTag="div" {...props} > {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code className={className} {...props}> {children} </code> ); }, }} > {post.content} </ReactMarkdown> </div> </article> ); }
Alle components in de App Router zijn standaard Server Components:
tsx// Dit draait op de server - geen JavaScript naar de client gestuurd! export default async function UserDashboard() { const data = await fetch('https://api.example.com/user', { cache: 'no-store', // Dynamische data }); const user = await data.json(); return ( <div> <h1>Welkom, {user.name}</h1> <UserStats stats={user.stats} /> </div> ); }
Gebruik 'use client' voor interactieve components:
tsx'use client'; import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Teller: {count}</p> <button onClick={() => setCount(count + 1)}> Verhoog </button> </div> ); }
typescript// Statische data (gegenereerd tijdens build time) async function getStaticData() { const res = await fetch('https://api.example.com/posts', { cache: 'force-cache', // Standaard gedrag }); return res.json(); } // Dynamische data (opgehaald bij elk request) async function getDynamicData() { const res = await fetch('https://api.example.com/user', { cache: 'no-store', }); return res.json(); } // Gerevalideerde data (gecached met tijd-gebaseerde revalidatie) async function getRevalidatedData() { const res = await fetch('https://api.example.com/products', { next: { revalidate: 3600 }, // Revalideer elk uur }); return res.json(); }
typescriptimport type { Metadata } from 'next'; export const metadata: Metadata = { title: 'Next.js 15 Gids', description: 'Leer alles over Next.js 15', openGraph: { title: 'Next.js 15 Gids', description: 'Leer alles over Next.js 15', images: ['/og-image.jpg'], }, twitter: { card: 'summary_large_image', title: 'Next.js 15 Gids', description: 'Leer alles over Next.js 15', images: ['/og-image.jpg'], }, };
tsx// app/blog/loading.tsx export default function Loading() { return ( <div className="container mx-auto px-4 py-16"> <div className="animate-pulse"> <div className="h-8 bg-gray-200 rounded w-1/4 mb-8"></div> <div className="grid gap-8 md:grid-cols-3"> {[...Array(6)].map((_, i) => ( <div key={i} className="bg-gray-100 p-6 rounded-lg"> <div className="h-4 bg-gray-200 rounded mb-4"></div> <div className="h-4 bg-gray-200 rounded mb-4"></div> <div className="h-4 bg-gray-200 rounded w-2/3"></div> </div> ))} </div> </div> </div> ); }
tsximport Image from 'next/image'; export default function Hero() { return ( <Image src="/hero.jpg" alt="Hero afbeelding" width={1200} height={600} priority // Laad direct voor above-the-fold afbeeldingen placeholder="blur" // Toon blur tijdens laden blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." /> ); }
tsximport { Inter, Roboto_Mono } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', }); const robotoMono = Roboto_Mono({ subsets: ['latin'], display: 'swap', }); export default function RootLayout({ children }) { return ( <html lang="nl" className={inter.className}> <body>{children}</body> </html> ); }
typescript// next.config.ts const config: NextConfig = { // Schakel bundle analyse in bundlePagesRouterDependencies: true, // Optimaliseer packages transpilePackages: ['@my-org/ui'], // Schakel productie optimalisaties in compiler: { removeConsole: process.env.NODE_ENV === 'production', }, };
Laten we verschillende programmeertalen testen om er zeker van te zijn dat syntax highlighting werkt:
javascriptconst greet = (name) => { console.log(`Hallo, ${name}!`); return `Welkom bij Next.js 15`; }; // Array methoden const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2);
pythondef fibonacci(n): """Genereer Fibonacci reeks""" if n <= 1: return n else: return fibonacci(n-1) + fibonacci(n-2) # List comprehension squares = [x**2 for x in range(10)]
css.container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; padding: 2rem; } .card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 0.5rem; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); }
json{ "name": "my-nextjs-app", "version": "1.0.0", "scripts": { "dev": "next dev --turbo", "build": "next build", "start": "next start" }, "dependencies": { "next": "^15.5.0", "react": "^19.0.0" } }
bash#!/bin/bash # Deploy script echo "Next.js app deployen..." npm run build npm run test if [ $? -eq 0 ]; then echo "✓ Build succesvol" npm run deploy else echo "✗ Build mislukt" exit 1 fi
bash# Installeer Vercel CLI npm i -g vercel # Deploy vercel # Productie deployment vercel --prod
dockerfile# Dockerfile FROM node:18-alpine AS base # Dependencies FROM base AS deps WORKDIR /app COPY package*.json ./ RUN npm ci # Builder FROM base AS builder WORKDIR /app COPY /app/node_modules ./node_modules COPY . . RUN npm run build # Runner FROM base AS runner WORKDIR /app ENV NODE_ENV production COPY /app/public ./public COPY /app/.next/standalone ./ COPY /app/.next/static ./.next/static EXPOSE 3000 ENV PORT 3000 CMD ["node", "server.js"]
bash# Build voor productie npm run build # Start productie server npm start # Of gebruik PM2 pm2 start npm --name "nextjs-app" -- start
bashnpm install next@latest react@latest react-dom@latest
typescript// Update dynamische API calls naar async const cookieStore = await cookies(); const headersList = await headers();
json{ "scripts": { "dev": "next dev --turbo" } }
bashnpm run build npm run start
Next.js 15 vertegenwoordigt een enorme sprong voorwaarts in webontwikkeling. Met React 19 support, Turbopack's razendsnelle performance, verbeterde TypeScript ondersteuning, en krachtige nieuwe features zoals het Form component en async request API's, is het het perfecte framework voor het bouwen van moderne webapplicaties.
De combinatie van:
...maakt Next.js 15 de beste versie tot nu toe.
Of je nu een persoonlijke blog bouwt met Markdown rendering (zoals deze post!), een e-commerce platform, of een complex dashboard, Next.js 15 biedt alle tools die je nodig hebt om snelle, schaalbare, production-ready applicaties te bouwen.
Bij Joulyan IT specialiseren we ons in het bouwen van high-performance webapplicaties met cutting-edge technologieën zoals Next.js 15. Van planning tot deployment, we helpen bedrijven de nieuwste webtechnologieën te benutten om uitzonderlijke gebruikerservaringen te creëren.
Wil je je volgende project bespreken? Neem contact op om te ontdekken hoe we je visie tot leven kunnen brengen met Next.js 15.
Laatst bijgewerkt: 22 november 2025