@nuraly/lumenjs
v0.16.0
Published
Full-stack Lit web component framework with file-based routing, server loaders, SSR, and API routes
Maintainers
Readme
LumenJS
Mirror: This repository is a read-only mirror of the original private repository, automatically synced on push to main.
A full-stack web framework for Lit web components. File-based routing, server loaders, real-time subscriptions (SSE), SSR with hydration, nested layouts, API routes, i18n, and a visual editor — all powered by Vite.
Getting Started
npx @nuraly/lumenjs create my-app
cd my-app
npm install
npx lumenjs devOpen http://localhost:3000 — you're live.
Templates
npx @nuraly/lumenjs create my-app # default starter
npx @nuraly/lumenjs create my-blog --template blog # blog with file-based posts
npx @nuraly/lumenjs create my-dash --template dashboard # real-time dashboard with SSEFeatures
- File-based routing —
pages/about.ts→/about,pages/blog/[slug].ts→/blog/:slug - Server loaders — fetch data server-side, hydrate on the client
- Live data (SSE) —
subscribe()pushes real-time updates to the browser - SSR + hydration — pre-rendered HTML with
@lit-labs/ssr, zero flash - Nested layouts —
_layout.tswraps child routes, persists across navigation - API routes —
api/users.ts→ REST endpoints with file uploads - i18n — URL-prefix routing, JSON translations, SSR-safe
- Visual editor — click-to-select, inline text editing, file browser
- Tailwind & NuralyUI — one command to add integrations
Pages
Pages are Lit components in pages/. The file path determines the URL.
// pages/index.ts
import { LitElement, html, css } from 'lit';
export class PageIndex extends LitElement {
static styles = css`:host { display: block; }`;
render() {
return html`<h1>Hello, LumenJS!</h1>`;
}
}| File | URL | Tag |
|---|---|---|
| pages/index.ts | / | <page-index> |
| pages/about.ts | /about | <page-about> |
| pages/blog/[slug].ts | /blog/:slug | <page-blog-slug> |
| pages/[...slug].ts | /* (catch-all) | <page-slug> |
Loaders
Export a loader() to fetch data server-side. It runs on SSR and via /__nk_loader/<path> during client-side navigation. Automatically stripped from client bundles.
Declare each returned key as its own property — the framework spreads loader data onto the element automatically.
// pages/blog/[slug].ts
export async function loader({ params }) {
const post = await db.posts.findOne({ slug: params.slug });
if (!post) return { __nk_redirect: true, location: '/404', status: 302 };
return { post };
}
export class BlogPost extends LitElement {
static properties = { post: { type: Object } };
post: any = null;
render() {
return html`<h1>${this.post?.title}</h1>`;
}
}Splitting large loaders
For folder routes (pages/foo/index.ts), you can move the loader into a co-located _loader.ts file. The framework discovers it automatically — no import or wrapper needed in the page file.
pages/
└── dashboard/
├── index.ts ← page component only
└── _loader.ts ← auto-discovered loader// pages/dashboard/_loader.ts
export async function loader({ user }) {
const stats = await db.getStats(user.id);
return { stats };
}// pages/dashboard/index.ts — no loader here at all
export class PageDashboard extends LitElement {
static properties = { stats: { type: Array } };
stats = [];
render() { ... }
}Both patterns work side by side — the inline loader always takes precedence. Only folder routes (index.ts) support _loader.ts discovery; flat pages (about.ts) keep the loader inline.
Loader Context
| Property | Type | Description |
|---|---|---|
| params | Record<string, string> | Dynamic route parameters |
| query | Record<string, string> | Query string parameters |
| url | string | Request pathname |
| headers | Record<string, any> | Request headers |
| locale | string | Current locale (when i18n is configured) |
Live Data (subscribe)
Export a subscribe() to push real-time data over Server-Sent Events.
// pages/dashboard.ts
export function subscribe({ push }) {
const interval = setInterval(() => {
push({ time: new Date().toISOString(), count: ++n });
}, 1000);
return () => clearInterval(interval);
}
export class PageDashboard extends LitElement {
static properties = {
time: { type: String },
count: { type: Number },
};
time = '';
count = 0;
render() {
return html`<p>Server time: ${this.time}</p>`;
}
}Each key from push() is spread as an individual property on the component — same as loader data. Return a cleanup function — it runs when the client disconnects.
Layouts
Create _layout.ts in any directory. It wraps all pages below it and persists across navigation.
// pages/_layout.ts
export class RootLayout extends LitElement {
render() {
return html`
<header>My App</header>
<main><slot></slot></main>
`;
}
}Layouts can have their own loader() and subscribe() for shared data (auth, notifications, etc.).
API Routes
Files in api/ become REST endpoints. Export named functions for each HTTP method.
// api/users/[id].ts
export async function GET(req) {
return { user: { id: req.params.id, name: 'Alice' } };
}
export async function POST(req) {
const { name } = req.body;
return { created: true, name };
}Supports JSON bodies, query params, dynamic routes, file uploads (req.files), and error handling via throw { status, message }.
i18n
// lumenjs.config.ts
export default {
i18n: { locales: ['en', 'fr'], defaultLocale: 'en' },
};import { t, setLocale } from '@lumenjs/i18n';
html`<h1>${t('home.title')}</h1>
<button @click=${() => setLocale('fr')}>FR</button>`;Translations live in locales/en.json, locales/fr.json. URL routing: /about (default), /fr/about (French). Server-rendered, no flash.
Integrations
npx lumenjs add tailwind # Tailwind CSS via @tailwindcss/vite// lumenjs.config.ts — NuralyUI auto-import
export default { integrations: ['nuralyui'] };Visual Editor
Start the dev server with --editor-mode to edit pages visually in the browser:
npx lumenjs dev --editor-modeClick elements to select them, edit properties and styles in the side panel, double-click text to edit inline, or ask the AI assistant to make changes for you. Everything saves directly to your source files.
AI Backend
The editor includes an AI assistant that can modify your components. It supports three backends:
Claude Code (recommended) — uses your Pro/Max subscription, no API key needed:
npm install -g @anthropic-ai/claude-code
claude login
npm install @anthropic-ai/claude-agent-sdk
npx lumenjs dev --editor-modeOpenCode — coding agent server, configure it with DeepSeek or any LLM provider:
npm install -g opencode
opencode serve # terminal 1
npx lumenjs dev --editor-mode # terminal 2Configure the connection: OPENCODE_URL (default http://localhost:4096) and OPENCODE_SERVER_PASSWORD if auth is required.
Set AI_BACKEND to force a specific backend (claude-code or opencode). Without it, the editor auto-detects: Claude Code (if CLI logged in) → OpenCode (fallback).
CLI
lumenjs create <name> [--template <default|blog|dashboard>]
lumenjs dev [--project <dir>] [--port <port>] [--editor-mode]
lumenjs build [--project <dir>] [--out <dir>]
lumenjs serve [--project <dir>] [--port <port>]
lumenjs add <integration>Production
npx lumenjs build --project ./my-app
npx lumenjs serve --project ./my-app --port 8080Outputs to .lumenjs/ — pre-built client assets, server modules, route manifest. The production server handles SSR, loaders, API routes, and gzip compression.
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
License
MIT
