
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A lightweight web framework for Bun with Next.js-style routing and zero-config builds.
Bun-native web framework — server-rendered JSX + lightweight client runtime
Melina.js is a web framework built for Bun. Pages are server-rendered JSX — write components that run on the server, render to HTML, and arrive at the browser instantly. Client interactivity is added via mount scripts — small .client.tsx files that hydrate specific parts of the page with a zero-dependency ~2KB VDOM runtime.
No React on the client. No hydration mismatch. No bundle bloat.
Server (Bun) Browser
┌──────────────┐ ┌──────────────┐
│ page.tsx │──HTML──▶ │ Static DOM │
│ layout.tsx │ │ │
│ api/route.ts│ │ .client.tsx │
│ middleware │ │ mount() │
│ SSG cache │ │ VDOM render │
└──────────────┘ └──────────────┘
app/page.tsx → /)layout.tsx at any level, composed automaticallypage.client.tsx adds interactivity without shipping Reactapp/api/*/route.ts with GET, POST, etc.app/post/[id]/page.tsx → /post/:idexport const ssg = true to pre-render at startup, serve from memory<Head> component — Declarative <title>, <meta> per page during SSRerror.tsx catches render errors with layout chromemiddleware.ts at any route level, runs root→leafpage.css or style.css scoped to route segments@tailwindcss/postcss supportAsyncGenerator from API routes for SSEdist/ folder — assets built and served from RAMnode_modules for bun:* imports and auto-stubs them for browser builds# Create a new project
npx melina init my-app
cd my-app
bun install
bun run server.ts
Or from scratch:
// server.ts
import { start } from 'melina';
await start({
appDir: './app',
port: 3000,
defaultTitle: 'My App',
// hotReload: true, // opt-in in dev
});
my-app/
├── app/
│ ├── layout.tsx # Root layout (wraps all pages)
│ ├── layout.client.tsx # Persistent client JS (survives navigation)
│ ├── globals.css # Global styles (Tailwind or plain CSS)
│ ├── page.tsx # Home page (/)
│ ├── page.client.tsx # Home page mount script
│ ├── page.css # Scoped CSS for home page
│ ├── middleware.ts # Root middleware (runs on every request)
│ ├── error.tsx # Error boundary
│ ├── about/
│ │ └── page.tsx # /about
│ ├── post/[id]/
│ │ └── page.tsx # /post/:id (dynamic route)
│ └── api/
│ └── messages/
│ └── route.ts # API: /api/messages
├── server.ts
└── package.json
Pages export a default function that returns JSX. These run only on the server — you can access databases, read files, call APIs directly:
// app/page.tsx
export default function HomePage() {
const posts = db.query('SELECT * FROM posts LIMIT 10');
return (
<main>
<h1>Latest Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
A page.client.tsx file adds interactivity to server-rendered HTML. Export a default mount() function — it receives the DOM after SSR:
// app/counter/page.client.tsx
import { render } from 'melina/client';
function Counter({ count, onIncrement }: { count: number; onIncrement: () => void }) {
return (
<div>
<span>{count}</span>
<button onClick={onIncrement}>+1</button>
</div>
);
}
export default function mount() {
const root = document.getElementById('counter-root');
if (!root) return;
let count = 0;
const update = () => {
render(<Counter count={count} onIncrement={() => { count++; update(); }} />, root);
};
update();
}
Key design decisions:
render(vnode, container) is the entire APILayouts wrap pages and compose automatically from root to leaf:
// app/layout.tsx
export default function RootLayout({ children }: { children: any }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>My App</title>
</head>
<body>
<nav><a href="/">Home</a></nav>
<main>{children}</main>
</body>
</html>
);
}
layout.client.tsx is a persistent mount script — it survives page navigations, ideal for global UI like nav highlights or notification systems.
<Head> ComponentDeclarative per-page head management during SSR:
import { Head } from 'melina/web';
export default function AboutPage() {
return (
<>
<Head>
<title>About Us — My App</title>
<meta name="description" content="Learn about our team" />
<link rel="canonical" href="https://example.com/about" />
</Head>
<main><h1>About Us</h1></main>
</>
);
}
Export HTTP method handlers:
// app/api/messages/route.ts
export async function GET(req: Request) {
const messages = await db.getMessages();
return Response.json(messages);
}
export async function POST(req: Request) {
const body = await req.json();
await db.createMessage(body);
return Response.json({ ok: true });
}
Streaming — Return an AsyncGenerator for Server-Sent Events:
export async function* GET(req: Request) {
for (let i = 0; i < 10; i++) {
yield `data: ${JSON.stringify({ count: i })}\n\n`;
await new Promise(r => setTimeout(r, 1000));
}
}
Opt in per page — pre-render at startup, serve from memory:
// Pre-render once, serve forever
export const ssg = true;
// Or with TTL (re-render after expiry)
export const ssg = { revalidate: 60 }; // seconds
export default function PricingPage() {
return <main><h1>Pricing</h1></main>;
}
middleware.ts files run before the page renders, root→leaf:
// app/middleware.ts
export default async function middleware(req: Request) {
const token = req.headers.get('authorization');
if (!token) {
return new Response('Unauthorized', { status: 401 });
}
// Return nothing to continue to the page
}
error.tsx catches render errors and displays them with full layout chrome:
// app/error.tsx
export default function ErrorPage({ error }: { error: { message: string } }) {
return (
<div>
<h1>Something went wrong</h1>
<p>{error.message}</p>
</div>
);
}
app/post/[id]/page.tsx → /post/:id
app/user/[userId]/page.tsx → /user/:userId
export default function PostPage({ params }: { params: { id: string } }) {
return <h1>Post #{params.id}</h1>;
}
Add page.css or style.css alongside any page — it's automatically injected only for that route:
/* app/dashboard/page.css */
.metric-card {
background: linear-gradient(135deg, #1a1a2e, #16213e);
border-radius: 12px;
padding: 24px;
}
Built-in Tailwind CSS v4 + PostCSS. Add globals.css in the app directory:
@import "tailwindcss";
@theme {
--color-primary: #0a0a0f;
--color-accent: #6366f1;
}
Melina auto-discovers globals.css, global.css, or app.css.
start(options)High-level entry point:
import { start } from 'melina';
await start({
appDir: './app',
port: 3000,
defaultTitle: 'My App',
});
serve(handler, options) + createAppRouter(options)Lower-level API for custom setups:
import { serve, createAppRouter } from 'melina';
const handler = createAppRouter({
appDir: './app',
defaultTitle: 'My App',
globalCss: './app/globals.css',
hotReload: true,
});
serve(handler, { port: 3000, hotReload: true });
render(vnode, container)The entire client API:
import { render, createElement } from 'melina/client';
render(<MyComponent />, document.getElementById('root'));
npx melina init <project-name> # Create new project from template
npx melina start # Start dev server
Run the built-in showcase to see every feature in action:
git clone https://github.com/7flash/melina.js.git
cd melina.js
bun install
bun run examples/showcase/server.ts
# → http://localhost:3000
The showcase includes:
<Head> componentMelina is intentionally small. We don't add features unless they solve a real problem that the existing primitives can't handle. Two features we've explicitly decided against:
The comparison table on the SSG page shows three strategies: SSR, Cached SSR, and SSG. Cached SSR does not exist as a framework feature — and we don't plan to add it.
The pitch for Cached SSR is: "Render on the first request, cache the HTML, serve the cache for subsequent requests until TTL expires." But SSG with revalidation already does this — better:
// This is all you need. No Cached SSR required.
export const ssg = { revalidate: 60 }; // re-render every 60 seconds
export default function PricingPage() {
const prices = db.getPrices(); // fresh data on each revalidation
return <main><PriceTable prices={prices} /></main>;
}
Here's the concrete comparison:
| Cached SSR | SSG with revalidate | |
|---|---|---|
| When cached | After first visitor requests | At startup (before any visitor) |
| First visitor | Pays full render cost | Instant response |
| Storage | JS string in memory (GC pressure) | ArrayBuffer (zero-copy, no GC) |
| Cache refresh | Next request after TTL expires triggers re-render | Background revalidation on timer |
| Invalidation | TTL only | TTL via revalidate, or manual via clearSSGCache() |
| Cold start | Slow (uncached) | Fast (pre-rendered) |
The critical difference: Cached SSR penalizes the first visitor with a full server render. SSG pre-renders at startup, so every visitor — including the first — gets an instant response. The revalidate option handles staleness automatically, and clearSSGCache() handles on-demand invalidation (e.g., after a webhook from your CMS).
If you need truly dynamic, per-request data (user-specific content, authenticated pages), use SSR. If you want caching, use SSG with revalidate. There's no use case where "SSR + cache the response" beats "SSG + periodic revalidation" — SSG is strictly better because it eliminates the cold-start penalty entirely.
In dev mode, Melina watches your client script dependency trees and auto-reloads the browser on save:
hot-reload.ts uses fs.watch() on directories containing client scripts and their imports/__melina_hmrEventSource client in the page triggers window.location.reload()Bun.Transpiler.scanImports() — only local imports are followedApps can also configure server-only packages via package.json:
{
"melina": {
"serverOnly": ["my-db-adapter", "internal-auth-lib"]
}
}
These packages will be stubbed with a Proxy in browser builds, preventing bun:* import errors.
src/
├── server/
│ ├── app-router.ts # Route matching, SSR pipeline, error boundaries
│ ├── build.ts # Asset build pipeline (JS, CSS, static files)
│ ├── serve.ts # HTTP server with measure-fn observability
│ ├── router.ts # File-based route discovery
│ ├── ssg.ts # Static site generation (pre-render + memory serve)
│ ├── ssr.ts # renderToString (VNode → HTML)
│ ├── head.ts # <Head> component (side-channel collection)
│ ├── imports.ts # Import map generation
│ ├── hot-reload.ts # Dev-only SSE hot reload + file watcher
│ └── types.ts # Shared types
├── client/
│ ├── render.ts # VDOM renderer + Fiber reconciler (~2KB)
│ ├── reconcilers/ # Pluggable diffing strategies
│ │ ├── keyed.ts # O(n log n) key-based with LIS
│ │ ├── sequential.ts # O(n) index-based
│ │ └── replace.ts # Full replace (baseline)
│ ├── jsx-runtime.ts # JSX transform for client bundles
│ ├── jsx-dom.ts # JSX-to-real-DOM for mount scripts
│ └── types.ts # VNode, Component, Props types
└── web.ts # Main entry point
Every operation is instrumented with measure-fn:
[a] ✓ Discover routes 8.10ms → 17 routes
[b] ... GET http://localhost:3000/
[b-a] ... Middleware: app
[b-a] ✓ Middleware: app 0.12ms
[b-b] ... Import page
[b-b] ✓ Import page 0.04ms
[b-c] ... SSR renderToString
[b-c] ✓ SSR renderToString 0.31ms
[build:d] ... Style: globals.css
[build:d] ✓ Style: globals.css 0.10ms
[b] ✓ GET http://localhost:3000/ 2.14ms
bun test
MIT © 7flash Import page 0.04ms [b-c] ... SSR renderToString [b-c] ✓ SSR renderToString 0.31ms [build:d] ... Style: globals.css [build:d] ✓ Style: globals.css 0.10ms [b] ✓ GET http://localhost:3000/ 2.14ms
### Running Tests
```bash
bun test
MIT © 7flash
FAQs
A lightweight web framework for Bun with Next.js-style routing and zero-config builds.
The npm package melina receives a total of 344 weekly downloads. As such, melina popularity was classified as not popular.
We found that melina demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.