meta-frameworks-overview
Meta-Frameworks Overview
What is a Meta-Framework?
A meta-framework builds on top of a UI library (React, Vue, Svelte) to provide:
UI Library (React, Vue, Svelte)
↓ adds
Meta-Framework Features:
├── File-based routing
├── Server-side rendering (SSR)
├── Static site generation (SSG)
├── API routes / backend integration
├── Code splitting & bundling
├── Image optimization
├── Deployment optimizations
└── Full-stack capabilities
Why Meta-Frameworks?
Without a Meta-Framework (Manual Setup)
Create React App
├── Configure Webpack/Vite
├── Set up React Router
├── Configure SSR manually (complex)
├── Set up Express/Node server
├── Configure code splitting
├── Set up environment variables
├── Configure production build
└── Handle deployment
With a Meta-Framework
npx create-next-app / npm create astro
├── Routing: ✓ automatic
├── SSR/SSG: ✓ built-in
├── API routes: ✓ included
├── Bundling: ✓ optimized
├── Deployment: ✓ streamlined
└── Start building: ✓ immediately
Framework Comparison Matrix
| Framework | UI Library | Rendering | Philosophy |
|---|---|---|---|
| Next.js | React | SSR, SSG, ISR, CSR | Full-stack React |
| Remix | React | SSR, progressive enhancement | Web standards, forms |
| Nuxt | Vue | SSR, SSG, ISR | Full-stack Vue |
| SvelteKit | Svelte | SSR, SSG, CSR | Compiler-first |
| Astro | Any (React, Vue, Svelte, etc.) | SSG, SSR, Islands | Content-first, minimal JS |
| Solid Start | Solid | SSR, SSG | Fine-grained reactivity |
| Qwik City | Qwik | Resumable | Zero hydration |
Next.js
Full-stack React framework by Vercel.
Key Features
- App Router (new): React Server Components, nested layouts
- Pages Router (legacy): Traditional file-based routing
- Rendering options: SSG, SSR, ISR, CSR per route
- API Routes: Backend endpoints within the same project
- Image/Font optimization: Built-in components
Routing Model
app/
├── page.tsx → /
├── about/page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/page.tsx → /blog/:slug
└── api/
└── route.ts → API endpoint
Rendering Decision
// Static (default - SSG)
export default function Page() {
return <div>Static content</div>;
}
// Dynamic (SSR)
export const dynamic = 'force-dynamic';
// ISR
export const revalidate = 60; // seconds
Best For
- Production React applications
- E-commerce, SaaS, marketing sites
- Teams wanting batteries-included solution
- Vercel deployment (optimized)
Remix
Full-stack React framework focused on web fundamentals.
Key Features
- Nested routes: Parallel data loading
- Progressive enhancement: Works without JavaScript
- Form handling: Built-in
<Form>with server actions - Error boundaries: Per-route error handling
- Web standards: Uses fetch, FormData, Response
Philosophy
Traditional SPA:
Click → JS preventDefault → Fetch API → Update State → Re-render
Remix:
Click → Form submits → Server action → Return data → Re-render
(Works without JS, enhanced with JS)
Routing Model
app/
├── routes/
│ ├── _index.tsx → /
│ ├── about.tsx → /about
│ ├── blog._index.tsx → /blog
│ └── blog.$slug.tsx → /blog/:slug
└── root.tsx → Root layout
Data Loading
// Parallel loading of nested route data
export async function loader({ params }) {
return json(await getPost(params.slug));
}
export async function action({ request }) {
const formData = await request.formData();
// Handle form submission
}
Best For
- Form-heavy applications
- Progressive enhancement requirements
- Teams preferring web standards
- Apps that should work without JS
Nuxt
Full-stack Vue framework.
Key Features
- Auto-imports: Components, composables auto-imported
- File-based routing: Vue components as routes
- Nitro server: Universal deployment
- Modules ecosystem: Rich plugin system
Routing Model
pages/
├── index.vue → /
├── about.vue → /about
└── blog/
├── index.vue → /blog
└── [slug].vue → /blog/:slug
Data Fetching
<script setup>
// Runs on server, cached
const { data } = await useFetch('/api/posts');
// Always fresh
const { data } = await useFetch('/api/posts', { fresh: true });
</script>
Best For
- Vue.js teams
- Content sites, applications
- Teams wanting Vue-specific optimizations
SvelteKit
Full-stack Svelte framework.
Key Features
- Compiler-first: Svelte compiles to vanilla JS
- Adapters: Deploy anywhere (Node, Vercel, Cloudflare, etc.)
- Load functions: Unified data loading
- Form actions: Built-in form handling
Routing Model
src/routes/
├── +page.svelte → /
├── +layout.svelte → Shared layout
├── about/+page.svelte → /about
└── blog/
├── +page.svelte → /blog
└── [slug]/+page.svelte → /blog/:slug
Data Loading
// +page.server.js - runs on server only
export async function load({ params }) {
return {
post: await getPost(params.slug)
};
}
Best For
- Performance-critical applications
- Teams preferring Svelte's approach
- Smaller bundle size requirements
Astro
Content-first framework with islands architecture.
Key Features
- Zero JS by default: Ships no JavaScript unless needed
- Islands architecture: Hydrate only interactive components
- Framework agnostic: Use React, Vue, Svelte together
- Content collections: First-class Markdown/MDX support
Routing Model
src/pages/
├── index.astro → /
├── about.astro → /about
└── blog/
├── index.astro → /blog
└── [slug].astro → /blog/:slug
Islands Pattern
---
import Header from './Header.astro'; // No JS
import Counter from './Counter.jsx'; // React
import Search from './Search.vue'; // Vue
---
<Header />
<!-- Islands: only these ship JS -->
<Counter client:load />
<Search client:visible />
Client Directives
| Directive | When Hydrates |
|---|---|
client:load |
Immediately on page load |
client:idle |
When browser is idle |
client:visible |
When component is visible |
client:media |
When media query matches |
client:only |
Skip SSR, client render only |
Best For
- Content-heavy sites (blogs, docs, marketing)
- Performance-first approach
- Teams using multiple UI frameworks
- Static sites with some interactivity
Qwik City
Framework with resumability (zero hydration).
Key Features
- Resumability: No hydration cost
- Lazy loading: Loads JS only when needed
- Familiar syntax: JSX-like templates
How Resumability Works
Traditional SSR:
Server render → Download all JS → Hydrate (re-execute) → Interactive
Qwik:
Server render + serialize state → Load tiny runtime → Resume → Interactive
(No re-execution, state already in HTML)
Best For
- Performance-critical applications
- Large applications with many components
- Teams wanting to eliminate hydration cost
Framework Selection Guide
By Project Type
| Project Type | Recommended |
|---|---|
| Marketing site | Astro, Next.js (SSG) |
| Blog/Docs | Astro, Next.js, SvelteKit |
| E-commerce | Next.js, Remix, Nuxt |
| Dashboard/App | Next.js, SvelteKit, Remix |
| Highly interactive app | Next.js, SvelteKit, Remix |
| Performance-critical | Astro, Qwik, SvelteKit |
By Team Expertise
| Team Knows | Recommended |
|---|---|
| React | Next.js, Remix, Astro |
| Vue | Nuxt, Astro |
| Svelte | SvelteKit, Astro |
| Multiple / Learning | Astro |
By Priorities
| Priority | Recommended |
|---|---|
| SEO | Any (all support SSR/SSG) |
| Minimal JavaScript | Astro, Qwik |
| Progressive enhancement | Remix |
| Largest ecosystem | Next.js |
| Fastest runtime | SvelteKit, Qwik |
| Most flexible | Astro |
Common Patterns Across Frameworks
File-Based Routing
All meta-frameworks use file structure for routes:
pages/about.tsx → /about
pages/[id].tsx → /123, /abc (dynamic)
pages/[...slug].tsx → /a/b/c (catch-all)
Layouts
Shared UI across routes:
layout.tsx (Next.js)
+layout.svelte (SvelteKit)
layouts/default.vue (Nuxt)
Data Loading
Fetching data before render:
getServerSideProps / loader functions (Next.js)
loader functions (Remix)
load functions (SvelteKit)
useFetch (Nuxt)
frontmatter + getStaticProps (Astro)
API Routes
Backend endpoints in the same project:
app/api/route.ts (Next.js)
app/routes/api.$.ts (Remix)
server/api/ (Nuxt)
src/pages/api/ (Astro)
Migration Considerations
From SPA to Meta-Framework
- Choose framework matching your UI library
- Migrate routing to file-based
- Move data fetching to server (loaders)
- Update build/deploy pipeline
- Add SSR/SSG as needed
Between Meta-Frameworks
- Routing patterns are similar (map file structure)
- Data fetching patterns differ (loaders, getServerSideProps, useFetch)
- Styling approaches may vary
- Deployment adapters differ
Deep Dive: Understanding Meta-Frameworks From First Principles
What Problems Do Meta-Frameworks Solve?
To understand meta-frameworks, you must understand the problems of building production apps WITHOUT them:
PROBLEM 1: Build Configuration Hell
// React alone gives you:
import React from 'react';
function App() { return <div>Hello</div>; }
// But browsers don't understand JSX. You need:
// - Babel (transpile JSX → JavaScript)
// - Webpack/Vite (bundle modules)
// - PostCSS (process CSS)
// - Asset handling (images, fonts)
// - Environment variables
// - Development server with HMR
// - Production build optimization
// That's ~500 lines of config before writing any app code
PROBLEM 2: Routing is Non-Trivial
// React gives you components, not routes
// You need React Router or similar:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// But then:
// - How to code-split per route?
// - How to preload routes?
// - How to handle nested layouts?
// - How to protect routes?
// - How to handle 404s?
PROBLEM 3: Data Fetching Complexity
// Basic fetching seems simple:
useEffect(() => {
fetch('/api/data').then(setData);
}, []);
// But production needs:
// - Loading states
// - Error handling
// - Caching
// - Deduplication
// - Revalidation
// - SSR considerations
PROBLEM 4: Server-Side Rendering is HARD
// SSR manually requires:
// - Node.js server (Express/Fastify)
// - renderToString on server
// - Hydration on client
// - Different entry points (server.js, client.js)
// - Matching router on both sides
// - Data serialization between server/client
// - Handling streaming
// - Environment differences (window undefined on server)
META-FRAMEWORK SOLUTION:
All of the above is pre-configured and optimized.
Just write your components and routes.
The Layered Architecture Model
Meta-frameworks are built in layers:
┌─────────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION CODE │
│ (Pages, Components, API routes, Business Logic) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ META-FRAMEWORK LAYER │
│ (Next.js / Nuxt / SvelteKit / Remix / Astro) │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Router │ │ Builder │ │ Server │ │ Optimizer │ │
│ │ System │ │ System │ │ Runtime │ │ Layer │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ UI LIBRARY LAYER │
│ (React / Vue / Svelte / Solid) │
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Component │ │ State │ │ Virtual │ │
│ │ Model │ │ Mgmt │ │ DOM │ │
│ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ PLATFORM LAYER │
│ (Node.js, Deno, Bun, Edge) │
└─────────────────────────────────────────────────────────────────┘
How File-Based Routing Works Under the Hood
The magic of "file = route" is compile-time transformation:
// YOUR FILE STRUCTURE:
// app/
// ├── page.tsx
// ├── about/page.tsx
// └── blog/[slug]/page.tsx
// AT BUILD TIME, framework generates something like:
const routes = [
{
pattern: /^\/$/,
component: () => import('./app/page.tsx'),
type: 'page',
},
{
pattern: /^\/about$/,
component: () => import('./app/about/page.tsx'),
type: 'page',
},
{
pattern: /^\/blog\/([^/]+)$/,
component: () => import('./app/blog/[slug]/page.tsx'),
type: 'page',
params: ['slug'],
},
];
// The framework's router uses this table at runtime
function matchRoute(pathname) {
for (const route of routes) {
const match = pathname.match(route.pattern);
if (match) {
return {
component: route.component,
params: extractParams(route.params, match),
};
}
}
return null; // 404
}
Server Components: A Paradigm Shift
React Server Components (used by Next.js App Router) fundamentally change the component model:
// TRADITIONAL REACT (All components run everywhere):
// This runs on server (for SSR) AND client (for hydration)
function ProductPage() {
const [product, setProduct] = useState(null);
useEffect(() => {
// Runs only on client
fetch('/api/product').then(r => r.json()).then(setProduct);
}, []);
return <div>{product?.name}</div>;
}
// SHIPPED TO CLIENT:
// - ProductPage component code
// - useState implementation
// - useEffect implementation
// - All dependencies
// REACT SERVER COMPONENTS:
// This runs ONLY on server, never shipped to client
async function ProductPage() {
// Direct database access! No API needed!
const product = await db.products.findById(123);
// This HTML is sent, not the component code
return <div>{product.name}</div>;
}
// SHIPPED TO CLIENT:
// - Only the rendered HTML
// - Zero JavaScript for this component
// BUT INTERACTIVITY NEEDS CLIENT COMPONENTS:
'use client'; // This directive makes it a client component
function AddToCartButton({ productId }) {
const [loading, setLoading] = useState(false);
async function handleClick() {
setLoading(true);
await addToCart(productId);
setLoading(false);
}
return <button onClick={handleClick}>{loading ? '...' : 'Add'}</button>;
}
// This component IS shipped to client
The composition model:
// Server Component (default in App Router)
async function ProductPage({ params }) {
const product = await getProduct(params.id); // Server-only
return (
<div>
<h1>{product.name}</h1> {/* Static, no JS */}
<p>{product.description}</p> {/* Static, no JS */}
<AddToCartButton id={product.id} /> {/* Client Component, has JS */}
</div>
);
}
// Rendered output:
// <div>
// <h1>Product Name</h1> ← Plain HTML
// <p>Description here</p> ← Plain HTML
// <button>Add to Cart</button> ← Hydrated with JS
// </div>
Data Loading: The Core Pattern Differences
Each framework has a different philosophy:
// NEXT.JS APP ROUTER:
// Data fetching in the component itself (async components)
async function ProductPage({ params }) {
// This fetch is automatically deduplicated and cached
const product = await fetch(`/api/products/${params.id}`).then(r => r.json());
return <Product data={product} />;
}
// Caching controls:
// fetch(url, { cache: 'force-cache' }) // SSG-like
// fetch(url, { cache: 'no-store' }) // SSR-like
// fetch(url, { next: { revalidate: 60 } }) // ISR-like
// REMIX:
// Explicit loader/action separation
export async function loader({ params }) {
// Runs on server for GET requests
const product = await db.products.find(params.id);
return json(product);
}
export async function action({ request }) {
// Runs on server for POST/PUT/DELETE
const formData = await request.formData();
await db.products.update(formData);
return redirect('/products');
}
export default function ProductPage() {
const product = useLoaderData(); // Data from loader
return <Product data={product} />;
}
// SVELTEKIT:
// Separate load files
// +page.server.ts
export async function load({ params }) {
return {
product: await getProduct(params.id)
};
}
// +page.svelte
<script>
export let data; // Receives load() return value
</script>
<h1>{data.product.name}</h1>
// ASTRO:
// Frontmatter-based (build-time by default)
---
const product = await fetch('...').then(r => r.json());
---
<h1>{product.name}</h1>
Adapter Pattern: Deploy Anywhere
SvelteKit pioneered the adapter pattern, now common across frameworks:
YOUR CODE
│
▼
┌─────────────────────────────┐
│ Build Process │
│ (Framework-specific) │
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ Adapter │
│ (Platform-specific) │
└──────────────┬──────────────┘
│
┌──────────┼──────────┬──────────┐
▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ Node │ │Vercel │ │ CF │ │Netlify│
│Server │ │Funcs │ │Workers│ │Funcs │
└───────┘ └───────┘ └───────┘ └───────┘
How adapters work:
// Framework produces platform-agnostic output:
// - Static files (HTML, CSS, JS, images)
// - Server functions (request handlers)
// - Prerender information
// ADAPTER transforms to platform-specific format:
// adapter-node:
// Creates Node.js server with Express/Polka
// adapter-vercel:
// Creates Vercel Functions configuration
// Outputs to .vercel/output directory
// adapter-cloudflare:
// Creates Worker script
// Uses Cloudflare's fetch handler format
// adapter-static:
// Generates only static files
// No server runtime needed
Edge Runtime: The Modern Frontier
Edge computing brings code execution to CDN nodes:
// TRADITIONAL SSR:
// Request: Tokyo user → CDN (Tokyo) → Origin Server (Virginia)
// ↓
// Render HTML
// ↓
// Response: Tokyo user ← CDN (Tokyo) ← Origin Server (Virginia)
//
// Latency: 150-300ms for server response
// EDGE SSR:
// Request: Tokyo user → Edge Node (Tokyo)
// ↓
// Render HTML locally
// ↓
// Response: Tokyo user ← Edge Node (Tokyo)
//
// Latency: 10-50ms for server response
Edge runtime limitations:
// Edge runtime is NOT full Node.js
// It's a minimal JavaScript environment
// AVAILABLE:
// - fetch() API
// - Web Crypto API
// - Basic JavaScript APIs
// NOT AVAILABLE:
// - fs (file system)
// - Native Node modules
// - Full npm ecosystem
// - Long-running processes
// NEXT.JS EDGE RUNTIME:
export const runtime = 'edge';
export async function GET(request) {
// Must use fetch-based APIs
const data = await fetch('https://api.example.com/data');
return Response.json(await data.json());
}
Framework Philosophies Explained
Next.js Philosophy: "The Platform for React"
- Embrace React fully (including experimental features)
- Provide every possible rendering mode
- Deep Vercel integration
- Enterprise-ready out of the box
Remix Philosophy: "Web Standards First"
- Use browser APIs, not framework abstractions
- Forms work without JavaScript
- Progressive enhancement by default
- Don't fight the platform
Astro Philosophy: "Less JavaScript is More"
- Static by default, dynamic when needed
- Islands of interactivity, not oceans
- Use any UI framework, or none
- Content sites deserve great tools
SvelteKit Philosophy: "Compiled, Not Runtime"
- Move work to compile time
- Smaller bundles through compilation
- Unified platform via adapters
- Developer experience matters
Build Process: What Actually Happens
When you run npm run build:
┌──────────────────────────────────────────────────────────────────┐
│ BUILD PIPELINE │
└──────────────────────────────────────────────────────────────────┘
STEP 1: ROUTE ANALYSIS
├── Scan file system for routes
├── Identify static vs dynamic routes
├── Build route manifest
└── Determine data dependencies
STEP 2: CODE TRANSFORMATION
├── Transpile TypeScript → JavaScript
├── Transform JSX → createElement calls
├── Process CSS (PostCSS, Tailwind, etc.)
└── Optimize imports (tree shaking)
STEP 3: BUNDLING
├── Create entry points per route
├── Code split appropriately
├── Generate chunk hashes
└── Create source maps
STEP 4: STATIC GENERATION (SSG routes)
├── Execute getStaticProps / load functions
├── Render components to HTML strings
├── Write HTML files to output directory
└── Copy static assets
STEP 5: SERVER BUNDLE (SSR routes)
├── Bundle server-side code separately
├── Exclude client-only code
├── Generate server entry points
└── Create serverless function bundles (if applicable)
STEP 6: OPTIMIZATION
├── Minify JavaScript (Terser, ESBuild)
├── Minify CSS
├── Optimize images
├── Generate asset manifests
└── Create preload hints
OUTPUT:
├── .next/ or .output/ or dist/
│ ├── static/ (CDN-served assets)
│ ├── server/ (Server functions)
│ ├── routes-manifest (Route configuration)
│ └── build-manifest (Asset mapping)
Choosing the Right Framework: Decision Tree
START: What UI library does your team know?
│
├─► React
│ │
│ └─► What's your priority?
│ ├─► Maximum flexibility, enterprise features → Next.js
│ ├─► Web standards, progressive enhancement → Remix
│ └─► Static content with some interactivity → Astro (with React islands)
│
├─► Vue
│ │
│ └─► Full-stack Vue app → Nuxt
│ Static with Vue components → Astro (with Vue islands)
│
├─► Svelte
│ │
│ └─► Full-stack Svelte → SvelteKit
│ Static with Svelte components → Astro (with Svelte islands)
│
├─► None / Learning
│ │
│ └─► What are you building?
│ ├─► Content site (blog, docs) → Astro
│ ├─► Web application → SvelteKit (easiest learning curve)
│ └─► Enterprise app → Next.js (most resources, hiring)
│
└─► Maximum Performance
│
└─► Static content → Astro (zero JS by default)
Interactive app → Qwik (resumable, no hydration)
For Framework Authors: Building Meta-Frameworks
Implementation Note: The patterns and code examples below represent one proven approach to building meta-frameworks. Meta-framework architecture varies significantly—Next.js tightly integrates with React, Nuxt builds on Vue's ecosystem, and Astro is UI-agnostic. The direction shown here provides foundational architecture that most meta-frameworks share. Your implementation will depend on your target UI framework, deployment strategy, and the developer experience you want to provide.
Core Meta-Framework Architecture
// META-FRAMEWORK CORE ARCHITECTURE
class MetaFramework {
constructor(config) {
this.config = config;
this.plugins = [];
this.hooks = new HookSystem();
this.router = new FileBasedRouter();
this.bundler = new Bundler();
this.renderer = new UniversalRenderer();
}
// Plugin system
use(plugin) {
this.plugins.push(plugin);
plugin.install?.(this);
}
// Development server
async dev() {
await this.hooks.call('beforeDev');
// Scan routes
const routes = await this.router.scan(this.config.pagesDir);
// Start HMR bundler
await this.bundler.startDev({
routes,
onUpdate: (updates) => this.handleHMR(updates),
});
// Start dev server
const server = await this.createDevServer(routes);
await this.hooks.call('afterDev', { server });
}
// Production build
async build() {
await this.hooks.call('beforeBuild');
const routes = await this.router.scan(this.config.pagesDir);
// Client build
const clientManifest = await this.bundler.buildClient(routes);
// Server build
const serverManifest = await this.bundler.buildServer(routes);
// Static generation
await this.generateStatic(routes, { clientManifest, serverManifest });
// Generate deployment artifacts
await this.adapter.generate(this.config);
await this.hooks.call('afterBuild');
}
}
// Hook system for extensibility
class HookSystem {
constructor() {
this.hooks = new Map();
}
on(name, handler) {
if (!this.hooks.has(name)) {
this.hooks.set(name, []);
}
this.hooks.get(name).push(handler);
}
async call(name, context = {}) {
const handlers = this.hooks.get(name) || [];
for (const handler of handlers) {
await handler(context);
}
}
}
Building the Plugin System
// PLUGIN SYSTEM IMPLEMENTATION
class PluginManager {
constructor(framework) {
this.framework = framework;
this.plugins = [];
}
async register(plugin) {
// Validate plugin
if (!plugin.name) {
throw new Error('Plugin must have a name');
}
// Check dependencies
for (const dep of plugin.dependencies || []) {
if (!this.plugins.find(p => p.name === dep)) {
throw new Error(`Plugin ${plugin.name} requires ${dep}`);
}
}
// Initialize plugin
const instance = await plugin.setup?.(this.framework) || {};
// Register hooks
if (plugin.hooks) {
for (const [hook, handler] of Object.entries(plugin.hooks)) {
this.framework.hooks.on(hook, handler.bind(instance));
}
}
// Add Vite plugins
if (plugin.vitePlugins) {
this.framework.bundler.addPlugins(plugin.vitePlugins);
}
this.plugins.push({ ...plugin, instance });
}
}
// Example plugin
const imageOptimizationPlugin = {
name: 'image-optimization',
async setup(framework) {
return {
cache: new Map(),
};
},
hooks: {
async beforeBuild() {
console.log('Preparing image optimization...');
},
async transformAsset(context) {
if (!isImage(context.path)) return;
const optimized = await optimizeImage(context.content, {
quality: 80,
formats: ['webp', 'avif'],
});
return optimized;
},
},
vitePlugins: [
{
name: 'vite-plugin-images',
transform(code, id) {
if (id.endsWith('.jpg') || id.endsWith('.png')) {
// Transform image imports
}
},
},
],
};
Implementing Adapters (Deploy Anywhere)
// ADAPTER SYSTEM IMPLEMENTATION
class AdapterManager {
constructor() {
this.adapters = new Map();
}
register(name, adapter) {
this.adapters.set(name, adapter);
}
async build(adapterName, context) {
const adapter = this.adapters.get(adapterName);
if (!adapter) {
throw new Error(`Unknown adapter: ${adapterName}`);
}
return adapter.adapt(context);
}
}
// Node.js adapter
const nodeAdapter = {
name: 'node',
async adapt(context) {
const { serverEntry, staticDir, outputDir } = context;
// Generate Node.js server
const serverCode = `
import { createServer } from 'http';
import { handler } from './handler.js';
const server = createServer(async (req, res) => {
const response = await handler(req);
res.writeHead(response.status, Object.fromEntries(response.headers));
res.end(response.body);
});
server.listen(process.env.PORT || 3000);
`;
await fs.writeFile(`${outputDir}/index.js`, serverCode);
await fs.cp(staticDir, `${outputDir}/static`, { recursive: true });
return { type: 'node', entry: 'index.js' };
},
};
// Cloudflare Workers adapter
const cloudflareAdapter = {
name: 'cloudflare',
async adapt(context) {
const { serverEntry, staticDir, outputDir } = context;
// Generate Worker script
const workerCode = `
import { handler } from './handler.js';
export default {
async fetch(request, env, ctx) {
// Try static files first
const url = new URL(request.url);
const staticResponse = await env.ASSETS.fetch(request);
if (staticResponse.status !== 404) return staticResponse;
// Fall back to SSR handler
return handler(request, env, ctx);
},
};
`;
await fs.writeFile(`${outputDir}/_worker.js`, workerCode);
// Generate wrangler.toml
const wranglerConfig = `
name = "${context.config.name}"
main = "_worker.js"
compatibility_date = "2024-01-01"
[site]
bucket = "./static"
`;
await fs.writeFile(`${outputDir}/wrangler.toml`, wranglerConfig);
return { type: 'cloudflare-workers' };
},
};
// Vercel adapter
const vercelAdapter = {
name: 'vercel',
async adapt(context) {
const { routes, serverEntry, staticDir, outputDir } = context;
// Generate Vercel config
const vercelConfig = {
version: 3,
routes: [
// Static assets
{ src: '/static/(.*)', dest: '/static/$1' },
// API routes
...routes.filter(r => r.type === 'api').map(r => ({
src: r.path,
dest: `/api${r.path}`,
})),
// SSR routes
{ src: '/(.*)', dest: '/render' },
],
functions: {
'api/**/*.js': { runtime: 'nodejs20.x' },
'render.js': { runtime: 'nodejs20.x' },
},
};
await fs.writeFile(
`${outputDir}/vercel.json`,
JSON.stringify(vercelConfig, null, 2)
);
// Generate serverless functions
for (const route of routes) {
if (route.type === 'api') {
await generateAPIFunction(route, outputDir);
}
}
// Generate SSR function
await generateSSRFunction(context, outputDir);
return { type: 'vercel' };
},
};
Server Component Architecture
// SERVER COMPONENT IMPLEMENTATION
class ServerComponentRuntime {
constructor() {
this.componentCache = new Map();
this.clientReferences = new Map();
}
// Mark component as client boundary
registerClientReference(id, exportName) {
this.clientReferences.set(`${id}#${exportName}`, {
id,
exportName,
chunks: [], // Filled during build
});
}
// Render server component to RSC payload
async renderToPayload(element, context) {
const payload = [];
await this.walkTree(element, payload, context);
return payload;
}
async walkTree(element, payload, context) {
if (!element) return null;
// Primitive values
if (typeof element !== 'object') {
return element;
}
// Array of children
if (Array.isArray(element)) {
return Promise.all(element.map(e => this.walkTree(e, payload, context)));
}
const { type, props } = element;
// HTML element
if (typeof type === 'string') {
return {
$$typeof: Symbol.for('react.element'),
type,
props: {
...props,
children: await this.walkTree(props.children, payload, context),
},
};
}
// Client component reference
if (this.clientReferences.has(type.$$id)) {
const ref = this.clientReferences.get(type.$$id);
return {
$$typeof: Symbol.for('react.client.reference'),
$$id: ref.id,
$$name: ref.exportName,
props: await this.serializeProps(props),
};
}
// Server component - execute
const result = await type(props);
return this.walkTree(result, payload, context);
}
// Serialize props for client
async serializeProps(props) {
const serialized = {};
for (const [key, value] of Object.entries(props)) {
if (typeof value === 'function') {
// Server actions become references
serialized[key] = {
$$typeof: Symbol.for('react.server.reference'),
$$id: registerServerAction(value),
};
} else {
serialized[key] = value;
}
}
return serialized;
}
}
// Build-time transform for 'use client' directive
function transformClientDirective(code, id) {
if (!code.startsWith("'use client'") && !code.startsWith('"use client"')) {
return null;
}
// Parse exports
const exports = parseExports(code);
// Generate proxy module for server
const serverProxy = exports.map(exp =>
`export const ${exp} = { $$id: "${id}#${exp}", $$typeof: Symbol.for('react.client.reference') };`
).join('\n');
return serverProxy;
}
Build Pipeline Orchestration
// BUILD PIPELINE ORCHESTRATION
class BuildPipeline {
constructor(config) {
this.config = config;
this.steps = [];
}
addStep(step) {
this.steps.push(step);
}
async run() {
const context = {
config: this.config,
routes: [],
assets: new Map(),
manifest: {},
};
for (const step of this.steps) {
console.log(`[build] ${step.name}...`);
const start = Date.now();
try {
await step.execute(context);
console.log(`[build] ${step.name} (${Date.now() - start}ms)`);
} catch (error) {
console.error(`[build] ${step.name} failed:`, error);
throw error;
}
}
return context;
}
}
// Standard build steps
const buildSteps = [
{
name: 'scan-routes',
async execute(ctx) {
ctx.routes = await scanRoutes(ctx.config.pagesDir);
},
},
{
name: 'bundle-client',
async execute(ctx) {
ctx.clientBuild = await bundleClient({
routes: ctx.routes,
outDir: `${ctx.config.outDir}/client`,
});
},
},
{
name: 'bundle-server',
async execute(ctx) {
ctx.serverBuild = await bundleServer({
routes: ctx.routes,
outDir: `${ctx.config.outDir}/server`,
});
},
},
{
name: 'prerender-static',
async execute(ctx) {
const staticRoutes = ctx.routes.filter(r => !r.isDynamic);
await Promise.all(staticRoutes.map(r => prerenderRoute(r, ctx)));
},
},
{
name: 'generate-manifest',
async execute(ctx) {
ctx.manifest = {
routes: ctx.routes.map(r => ({
path: r.path,
type: r.type,
prerendered: r.prerendered,
})),
assets: Object.fromEntries(ctx.assets),
buildId: Date.now().toString(36),
};
await fs.writeFile(
`${ctx.config.outDir}/manifest.json`,
JSON.stringify(ctx.manifest, null, 2)
);
},
},
{
name: 'adapt',
async execute(ctx) {
const adapter = getAdapter(ctx.config.adapter);
await adapter.adapt(ctx);
},
},
];
Related Skills
- See web-app-architectures for SPA vs MPA
- See rendering-patterns for SSR/SSG/ISR
- See hydration-patterns for hydration strategies
- See routing-patterns for routing concepts
More from kinfe123/fm-skills
middleware-patterns
Explains middleware concepts, patterns, and implementations. Covers server middleware, edge middleware, request/response pipelines, and common use cases like auth, logging, and CORS. Use when implementing middleware or understanding request processing pipelines.
12routing-patterns
Explains client-side routing, server-side routing, file-based routing, and navigation patterns in web applications. Use when implementing routing, understanding how navigation works in SPAs vs MPAs, or configuring routes in meta-frameworks.
9rendering-patterns
Explains web rendering strategies including Client-Side Rendering (CSR), Server-Side Rendering (SSR), Static Site Generation (SSG), Incremental Static Regeneration (ISR), and streaming. Use when deciding rendering strategy, optimizing performance, or understanding when content is generated and sent to users.
9web-app-architectures
Explains Single Page Applications (SPA), Multi Page Applications (MPA), and hybrid architectures. Use when discussing app architecture decisions, comparing SPA vs MPA, explaining how traditional vs modern web apps work, or when building a new web application.
9seo-fundamentals
Explains SEO for web applications including crawling, indexing, Core Web Vitals, structured data, and SEO challenges with SPAs. Use when optimizing for search engines, discussing SEO implications of architecture decisions, or implementing SEO best practices.
5build-pipelines-bundling
Explains JavaScript bundling, code splitting, chunking strategies, tree shaking, and build pipelines. Use when optimizing bundle size, understanding how modern build tools work, configuring Webpack/Vite/esbuild, or debugging build output.
5