DEV Community

Cover image for I built "Next.js for the terminal" in TypeScript — here's the architecture
OmarMusayev
OmarMusayev

Posted on

I built "Next.js for the terminal" in TypeScript — here's the architecture

A few months ago I started building terminaltui — a TypeScript framework for interactive terminal apps. The pitch is "Next.js for the terminal": write a pages/ directory of TS files, get a fully navigable TUI with file-based routing, components, themes, and SSH hosting.

It just hit v1.8.1 on npm with 2,142 passing tests. You can try it in zero seconds:

npx terminaltui try
Enter fullscreen mode Exit fullscreen mode

That opens a 5-page guided tour of the framework — no install, no signup.

This post is the technical writeup: how the file-based router works, the layout engine, and the SSH hosting bit. If you've used Ink (React for the terminal), terminaltui is in the same neighborhood but a step further — it ships routing, layout, and themes as defaults instead of "bring your own React patterns."

The file-based router

A terminaltui project is just two things: config.ts and pages/.

// pages/about.ts
import { card, markdown } from "terminaltui";

export const metadata = {
  label: "About",
  icon: "",
  order: 2,
};

export default function About() {
  return [
    card({ title: "About me", body: "Developer." }),
    markdown("Some markdown here."),
  ];
}
Enter fullscreen mode Exit fullscreen mode

File paths map to routes the same way Next.js does:

pages/
  index.ts            → /
  about.ts            → /about
  projects/
    index.ts          → /projects
    [slug].ts         → /projects/:slug
Enter fullscreen mode Exit fullscreen mode

Dynamic routes use bracket syntax. The page function gets { params: { slug: string } }.

Under the hood, the framework walks the pages/ directory at boot, compiles each TS file via esbuild (or imports directly under tsx in dev mode), and builds a route table. Spatial navigation is automatic — arrow keys move to the nearest focusable item by screen position. You never write a tabIndex.

You can read the router source on GitHub if you want to see how the scanner walks the directory and how dynamic segments get parsed.

The layout engine

terminaltui has a 12-column grid that behaves like CSS grid + flexbox:

row([
  col(sidebarBlocks, { span: 3, xs: 12 }),
  col(mainBlocks,    { span: 9, xs: 12 }),
], { gap: 1 });
Enter fullscreen mode Exit fullscreen mode

Breakpoints kick in at 60, 90, and 120 terminal columns. Each col can nest more rows. The engine measures each block's content (with stringWidth() for proper CJK/emoji widths), computes a flexbox-like layout, then renders.

Where this matters: most TUI frameworks make you do absolute positioning or rely on a parent that does. With a grid that wraps responsively, the same page renders correctly in a 200-col Kitty window AND a 60-col Apple Terminal window. (Yes, Apple Terminal gets a 256-color fallback because it can't handle truecolor escapes.)

SSH hosting in one command

The thing I'm most happy about: every terminaltui app can be hosted over SSH without touching a single SSH config file.

terminaltui serve --port 2222
Enter fullscreen mode Exit fullscreen mode

That spins up an SSH server (using ssh2 underneath) that accepts any client, allocates a PTY, and renders the app inside the session. Each connection gets its own isolated runtime — concurrent sessions don't clobber each other's state. The framework auto-detects the client's TERM and color depth.

Connect from anywhere with:

ssh user@your-server -p 2222
Enter fullscreen mode Exit fullscreen mode

This means your CLI doesn't have to be installed on the connecting side. You can ship a "live demo" by running serve once on a VPS — anyone with SSH can experience your app.

Source for the SSH server if you want to see how the multi-session isolation works (it uses Node's AsyncLocalStorage so each session sees its own runtime context).

Why TypeScript instead of Go or Rust?

I get this question a lot. Honest answer: most existing TUI frameworks are written in Go (Bubble Tea), Rust (Ratatui), or Python (Textual). Those are great languages but they're all new dependencies for a JS/TS team. terminaltui ships as a regular npm package — your existing CI/CD, your existing types, your existing editor. No new language, no new build chain.

Plus: I wanted to write import { card, row, col } from "terminaltui" and have it Just Work. TypeScript's structural typing + esbuild's speed makes the developer loop feel like React in 2026, not like ncurses in 1990.

What's not great yet

Being honest:

  • Docs are README-only. A real docs site is coming. For now the README + the bundled demos (npx terminaltui demo restaurant, etc.) are the manual.
  • No Windows ConPTY fast path. Works on Windows via a fallback, but native ConPTY support would speed it up.
  • No hot-reload for api/ routes in dev. Pages hot-reload fine; API endpoints currently require a restart.

If any of those bother you, the issues tab is open.

Try it

# Tour the framework (no install)
npx terminaltui try

# Scaffold a real project
npx terminaltui init my-site

# Or run any of the 11 bundled demos
npx terminaltui demo mac-monitor    # live macOS activity monitor
npx terminaltui demo dashboard      # a portfolio dashboard
npx terminaltui demo conference     # a tech conference site
Enter fullscreen mode Exit fullscreen mode

Source is MIT-licensed on GitHub. v1.8.1 is on npm. The landing page lives at terminaltui.dev.

If you build something with it, I'd love to see it.

Top comments (0)