Next.js Guide
Run a full Next.js development server in the browser with App Router, Pages Router, HMR, CSS Modules, and API routes.
Setup
The Next.js dev server needs three things: a VirtualFS to store your files, a NextDevServer to process requests, and a ServerBridge to serve them in an iframe.
import { VirtualFS } from 'almostnode';
import { NextDevServer } from 'almostnode/next';
import { getServerBridge } from 'almostnode';
const vfs = new VirtualFS();
const server = new NextDevServer(vfs);
const bridge = getServerBridge();
// Initialize the service worker
await bridge.initServiceWorker();
// Register the server on a port
bridge.registerServer(server, 3000);
// Point an iframe to the dev server
iframe.src = bridge.getServerUrl(3000);
App Router
The App Router uses file-based routing with /app directory conventions. Each directory can contain page.tsx, layout.tsx, loading.tsx, and error.tsx.
Basic Page
// Create a root layout
vfs.writeFileSync('/app/layout.tsx', `
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
);
}
`);
// Create the home page
vfs.writeFileSync('/app/page.tsx', `
export default function Home() {
return <h1>Welcome!</h1>;
}
`);
// Create a nested route: /about
vfs.writeFileSync('/app/about/page.tsx', `
export default function About() {
return <h1>About us</h1>;
}
`);
Dynamic Routes
Use bracket notation for dynamic segments:
// /app/posts/[id]/page.tsx → matches /posts/123
vfs.writeFileSync('/app/posts/[id]/page.tsx', `
export default function Post({ params }) {
return <h1>Post {params.id}</h1>;
}
`);
Route Groups
Route groups use (groupName) directories. They're transparent in the URL — useful for organizing code without affecting the path:
// /app/(marketing)/pricing/page.tsx → /pricing
vfs.writeFileSync('/app/(marketing)/pricing/page.tsx', `
export default function Pricing() {
return <h1>Plans</h1>;
}
`);
Pages Router
The Pages Router uses the /pages directory. Each file becomes a route:
// /pages/index.jsx → /
vfs.writeFileSync('/pages/index.jsx', `
export default function Home() {
return <h1>Pages Router home</h1>;
}
`);
// /pages/about.jsx → /about
vfs.writeFileSync('/pages/about.jsx', `
export default function About() {
return <h1>About</h1>;
}
`);
API Routes
Files in /pages/api/ (Pages Router) or /app/api/.../route.ts (App Router) create API endpoints:
// Pages Router API route
vfs.writeFileSync('/pages/api/hello.js', `
export default function handler(req, res) {
res.status(200).json({ message: 'Hello!' });
}
`);
// App Router route handler
vfs.writeFileSync('/app/api/hello/route.ts', `
export async function GET(request) {
return Response.json({ message: 'Hello!' });
}
`);
CSS Modules
Import .module.css files to get scoped class names:
vfs.writeFileSync('/app/styles.module.css', `
.title {
color: blue;
font-size: 2rem;
}
.subtitle {
color: gray;
}
`);
vfs.writeFileSync('/app/page.tsx', `
import styles from './styles.module.css';
export default function Home() {
return (
<div>
<h1 className={styles.title}>Hello</h1>
<p className={styles.subtitle}>World</p>
</div>
);
}
`);
Classes are scoped with a hash to prevent collisions between components.
Hot Module Replacement
The dev server supports HMR through React Refresh. When you change a file in the VFS, the server detects it and pushes updates to the browser without a full page reload:
// Initial page
vfs.writeFileSync('/app/page.tsx', `
export default function Home() {
return <h1>Version 1</h1>;
}
`);
// Later, update the file — HMR kicks in automatically
vfs.writeFileSync('/app/page.tsx', `
export default function Home() {
return <h1>Version 2</h1>;
}
`);
Configuration
You can provide a next.config.js for path aliases and other options:
vfs.writeFileSync('/next.config.js', `
module.exports = {
experimental: {
paths: {
'@/*': ['./src/*'],
'@components/*': ['./src/components/*']
}
}
};
`);