Melina.js
Now available for Bun

Melina.js
Streaming-First Web Framework

Build blazing fast web applications with zero configuration. Experience immediate Time to First Contentful Paint with our streaming-first architecture.

Melina.js Core Features

Performance

Streaming Performance

Melina.js delivers blazing fast performance with true streaming-first rendering. Your users see content instantly, even while data is still loading.

Chunk Map

Smart Chunk Mapping

Automatic chunk mapping ensures only the code you need is loaded, optimizing both speed and resource usage for every page.

Build Tool

Modern Build Tooling

Enjoy a modern, zero-config build pipeline. Melina.js integrates seamlessly with your workflow for fast development and reliable production builds.

Melina.js Banner

Built to run on Bun, a fast all in one JavaScript runtime, Melina.js leverages Bun's native bundler (which is up to 200x faster than Webpack) to offer a seamless development experience.
Unlike other frameworks such as Next.js, Melina.js eliminates the need for configuration.

Features

  • 🚀
    Simplified Setup
    Define a server handler and start building.
  • 🌊
    Streaming by Default
    Return AsyncGenerators from your handler for immediate Time to First Contentful Paint.
  • 🧩
    Dynamic Import Maps
    Generate modern ES module import maps from your package.json on the fly.
  • On-Demand Asset Building
    Client-side JavaScript and CSS are built when requested during development, and cached in production.
  • 📏
    Framework Agnostic
    Works with React, Vue, Svelte, or vanilla JS on the client-side.
  • 📊
    Built-in Performance Measurement
    Debug and optimize with ease using the measure utility.
  • 🔥
    Tailwind CSS JIT
    Seamless Tailwind CSS integration for your assets.

How It Works

Melina.js simplifies web application delivery with a handler-centric approach:

1. Request Handling

When a request comes in, it's routed to the main handler function you provide to serve().

2. Server-Side Logic

Your handler processes the request. You can implement routing, API endpoints, or page generation logic.

3. Streaming HTML

For HTML pages, your handler can return an AsyncGenerator<string>. Melina immediately starts streaming the first chunk of HTML to the browser, allowing instant parsing and rendering.

4. Asset Serving

Use asset(filePath) in your server-side logic to get a URL for client-side JS or CSS.
Development: Assets are built on-the-fly by Bun.
Production: Assets are built once and served with long-cache headers.

5. Import Map Injection

Use imports([...dependencies]) to generate an import map from package.json. Inject this into your HTML stream for bare module specifiers.

6. Data Injection

Inject server-side data into the HTML stream by embedding a <script> tag (e.g., window.SERVER_DATA) for the client to pick up.

This approach significantly improves perceived performance by prioritizing Time to First Contentful Paint (TTFCP) and enabling progressive rendering.

Quick Start

React with Tailwind CSS Example

  1. Install dependencies for the example:
    bun add react react-dom react-client
    bun add -d @types/react @types/react-dom tailwindcss bun-plugin-tailwind
  2. Create your React App Component (App.tsx):
    // ./App.tsx
    import React from 'react';
    
    // Make sure serverData is typed appropriately for your app
    // For this example, we expect { now: string }
    interface ServerData {
      now?: string;
      message?: string;
    }
    
    interface AppProps {
      serverData: ServerData;
    }
    
    const App: React.FC<AppProps> = ({ serverData }) => {
      return (
        <div className="p-4">
          <h1 className="text-2xl font-bold mb-2">Hello from Melina.js & React!</h1>
          <p className="text-lg">Data from server:</p>
          <pre className="bg-gray-100 p-3 rounded mt-1 text-sm">
            {JSON.stringify(serverData, null, 2)}
          </pre>
        </div>
      );
    };
    
    export default App;
  3. Create a client-side entrypoint (App.client.tsx):
    // ./App.client.tsx
    import React from 'react';
    import { createRoot } from "react-dom/client";
    import App from './App';
    
    declare global {
      interface Window {
        SERVER_DATA: any;
      }
    }
    
    const serverData = window.SERVER_DATA || { message: "No server data received" };
    
    createRoot(document.getElementById("root")!).render(
      <React.StrictMode>
        <App serverData={serverData} />
      </React.StrictMode>
    );
  4. Create your Tailwind CSS entrypoint (App.css):
    /* ./App.css */
    @import "tailwindcss" source("./");
    *Ensure your tailwind.config.js content array points to your .tsx files, e.g., content: ["./*.{(html, js, jsx, ts, tsx)}"]
  5. Create your server file (server.ts):
    // server.ts
    import path from "path";
    import { useServer, measure } from "melinajs";
    
    const { serve, asset, imports } = useServer();
    
    const generatedImportMaps = await measure(
      async () => imports(['react', 'react-dom/client', 'react/jsx-dev-runtime']),
      "Generate Import Maps"
    );
    
    const importMapScript = `
        <script type="importmap">
          ${JSON.stringify(generatedImportMaps, null, 2)}
        </script>
    `;
    
    async function* streamReactPage(req: Request) {
      const requestId = req.headers.get("X-Request-ID") || "unknown";
      yield `
        <!DOCTYPE html>
        <html>
        <head>
          <title>Melina + React</title>
          ${importMapScript}
          <script src="${await asset(
            path.join(__dirname, "App.client.tsx")
          )}" type="module" defer></script>
          <link rel="stylesheet" href="${await asset(
            path.join(__dirname, "App.css")
          )}" />
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
        </head>
        <body>
          <div id="root">
            <div class="p-4 text-xl text-gray-500">Loading app...</div>
          </div>
      `;
    
      // Simulate some async data fetching
      const serverData = await measure(async () => {
        await Bun.sleep(50); // Simulate delay
        return {
          now: new Date().toISOString(),
          message: "Data fetched on the server!"
        };
      }, "Fetch Server Data", { requestId });
    
      yield `
          <script>
            window.SERVER_DATA = ${JSON.stringify(serverData)};
          </script>
        </body>
        </html>
      `;
    }
    
    serve(async (req: Request) => {
      const url = new URL(req.url);
      if (url.pathname === '/') {
        return streamReactPage(req);
      }
      if (url.pathname === '/api/hello') {
        return Response.json({ message: "Hello from API" });
      }
      return new Response('Not Found', { status: 404 });
    });
    
    console.log("React example server running. Open http://localhost:3000");
  6. Update package.json (ensure type: "module"):
    {
      "name": "melina-react-example",
      "type": "module",
      "scripts": {
        "dev": "bun run server.ts",
        "start": "NODE_ENV=production bun run server.ts"
      },
      "dependencies": {
        "melinajs": "latest",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-client": "latest"
      },
      "devDependencies": {
        "@types/bun": "latest",
        "@types/react": "^18.2.0",
        "@types/react-dom": "^18.2.0",
        "bun-plugin-tailwind": "^0.0.15",
        "tailwindcss": "^3.3.0",
        "typescript": "^5.0.0"
      },
      "peerDependencies": {
        "typescript": "^5"
      }
    }
  7. Start your server:
    bun run dev
    Open http://localhost:3000 in your browser.

Ready to Get Started?

Join thousands of developers building fast web applications with Melinajs