Base frameworks like React, Vue, and Svelte solve UI rendering. Meta-frameworks like Next.js, Nuxt, and SvelteKit sit on top of them to solve architecture: routing, server-side rendering (SSR), data fetching, API routes, and deployment. They are the modern standard for full-stack web applications.
Step 1 — Rendering Strategies
Understanding the Acronyms
- CSR (Client-Side Rendering): Browser downloads empty HTML, loads JS, and renders the app. Bad for SEO, slow initial load.
- SSR (Server-Side Rendering): Server runs the framework code per-request, sending fully populated HTML to the browser. Great for SEO, slower server response.
- SSG (Static Site Generation): HTML is generated exactly once at build time. Extremely fast (served from CDN), but requires rebuild to update data.
- ISR (Incremental Static Regeneration): Pages are built statically, but re-generated in the background when traffic hits after a specific timeout.
Step 2 — Next.js App Router Overview
The Next.js App Router utilizes React Server Components by default. Routing is defined by the file system using specific conventions.
app/
├── layout.tsx # Root layout (HTML skeleton, Navbar, Footer)
├── page.tsx # The UI for route '/'
├── error.tsx # Catches runtime errors
├── loading.tsx # Suspense fallback shown while data fetches
└── dashboard/
├── layout.tsx # Nested layout for dashboard routes
├── page.tsx # Route: '/dashboard'
└── [id]/ # Dynamic route parameter
└── page.tsx # Route: '/dashboard/123'Step 3 — Next.js Data Fetching & Caching
Next.js extends the native fetch API with powerful caching and revalidation options.
// Server Component (default in app router)
export default async function Blog() {
// 1. Force cache (SSG equivalent)
const ssgRes = await fetch('https://api.example.com/posts', { cache: 'force-cache' });
// 2. No cache (SSR equivalent) - fetches on every request
const ssrRes = await fetch('https://api.example.com/live', { cache: 'no-store' });
// 3. Revalidate (ISR equivalent) - re-fetches background every 60s
const isrRes = await fetch('https://api.example.com/stats', {
next: { revalidate: 60 }
});
const posts = await isrRes.json();
return (
<main>
<h1>Latest Posts</h1>
<ul>
{posts.map((p: any) => <li key={p.id}>{p.title}</li>)}
</ul>
</main>
);
}Step 4 — Next.js Server Actions
Server Actions allow you to run server-side code directly from a client interaction (like a form submission) without writing a separate API route.
'use server'
import { revalidatePath } from 'next/cache';
import db from '@/lib/db';
export async function createPost(formData: FormData) {
const title = formData.get('title');
// Database mutation runs strictly on the server
await db.posts.insert({ title });
// Purge cache so new data shows immediately
revalidatePath('/blog');
}
// Usage in a component:
// <form action={createPost}>
// <input name="title" />
// <button type="submit">Post</button>
// </form>Step 5 — Nuxt.js (Vue's Meta-Framework)
Nuxt provides a stellar developer experience with zero-config auto-imports and an incredible unified server engine called Nitro.
<script setup lang="ts">
// useFetch automatically runs on server during SSR, then passes payload to client
// No double-fetching! Auto-imported globally.
const { data: posts, pending, error } = await useFetch('/api/posts')
</script>
<template>
<div>
<h1>Nuxt Blog</h1>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Error loading posts</div>
<ul v-else>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</div>
</template>// Nuxt Nitro Server Route
// Accessible at /api/posts
export default defineEventHandler(async (event) => {
// Database logic here
return [
{ id: 1, title: 'Nuxt is awesome' },
{ id: 2, title: 'Nitro makes it fast' }
]
})Step 6 — SvelteKit Overview
SvelteKit pairs +page.svelte files (the UI) with +page.server.ts files (the data loader). It enforces a very clean separation between server logic and client rendering.
import type { PageServerLoad } from './$types';
// Runs exclusively on the server
export const load: PageServerLoad = async ({ fetch, cookies }) => {
const session = cookies.get('session');
const res = await fetch(`/api/user/${session}`);
const user = await res.json();
// Return data to the page
return { user };
};<script lang="ts">
import type { PageData } from './$types';
// The data returned from the server load function is injected here
export let data: PageData;
</script>
<h1>Welcome, {data.user.name}</h1>
<p>Email: {data.user.email}</p>