Menu

Sol Framework

Experimental: Sol is under active development. APIs may change.

Sol is a full-stack SSR framework built on Luna UI and MoonBit. It provides Island Architecture with server-side rendering and partial hydration.

Features

  • Hono Integration - Fast, lightweight HTTP server

  • Island Architecture - Ship minimal JavaScript with smart triggers

  • File-based Routing - Pages and API routes from directory structure

  • Type-safe - MoonBit types flow from server to browser

  • Streaming SSR - Async content streaming support

  • CSR Navigation - SPA-like navigation with sol-link

  • Middleware - Railway Oriented Programming based middleware

  • Server Actions - CSRF-protected server-side functions

  • Nested Layouts - Hierarchical layout structures

  • SSG Mode - Static site generation (auto-detected from config)

Quick Start

# Create a new Sol project
npx @luna_ui/sol new myapp --user yourname
cd myapp

# Install dependencies
npm install

# Start development server
npm run dev

Project Structure

myapp/
├── moon.mod.json           # MoonBit module
├── package.json            # npm package
├── sol.config.json         # Sol configuration
├── app/
│   ├── server/             # Server components
│   │   ├── moon.pkg.json
│   │   └── routes.mbt      # routes() + config() + pages
│   ├── client/             # Client components (Islands)
│   │   └── counter/
│   │       ├── moon.pkg.json
│   │       └── counter.mbt # render + hydrate functions
│   └── __gen__/            # Auto-generated (sol generate)
│       ├── client/         # Client exports
│       └── server/         # Server entry point
└── static/
    └── loader.min.js       # Island loader

Guides

CLI Reference

sol new <name>

Create a new project.

sol new myapp --user mizchi         # Create mizchi/myapp package
sol new myapp --user mizchi --dev   # Use local luna paths (development)

sol dev

Start development server. Automatically runs:

  1. sol generate --mode dev - Code generation

  2. moon build - MoonBit build

  3. rolldown - Client bundle (if manifest exists)

  4. Start server

sol dev              # Default port 3000
sol dev --port 8080  # Custom port
sol dev --clean      # Clean cache and rebuild

sol build

Production build. Outputs to .sol/prod/.

sol build                 # JS target (default)
sol build --target wasm   # WASM target
sol build --skip-bundle   # Skip rolldown
sol build --skip-generate # Skip generation
sol build --clean         # Clean cache and rebuild

sol serve

Serve production build. Requires sol build first.

sol serve              # Default port 3000
sol serve --port 8080  # Custom port

sol generate

Generate code from sol.config.json.

sol generate                    # Use sol.config.json (default: dev)
sol generate --mode dev         # Development mode (.sol/dev/)
sol generate --mode prod        # Production mode (.sol/prod/)

sol clean

Delete generated files and cache.

sol clean  # Delete .sol/, app/__gen__/, target/

SolRoutes Definition

Declarative route definition with SolRoutes:

// app/server/routes.mbt

pub fn routes() -> Array[SolRoutes] {
  [
    // Page route
    SolRoutes::Page(
      path="/",
      handler=PageHandler(home_page),
      title="Home",
      meta=[],
    ),
    // GET API route
    SolRoutes::Get(
      path="/api/health",
      handler=ApiHandler(api_health),
    ),
    // POST API route
    SolRoutes::Post(
      path="/api/submit",
      handler=ApiHandler(api_submit),
    ),
    // Nested layout
    SolRoutes::Layout(
      segment="admin",
      layout=admin_layout,
      children=[
        SolRoutes::Page(path="/admin", handler=PageHandler(admin_dashboard), title="Admin"),
        SolRoutes::Page(path="/admin/users", handler=PageHandler(admin_users), title="Users"),
      ],
    ),
    // With middleware
    SolRoutes::WithMiddleware(
      middleware=[@middleware.cors(), @middleware.logger()],
      children=[
        SolRoutes::Get(path="/api/data", handler=ApiHandler(api_data)),
      ],
    ),
  ]
}

pub fn config() -> RouterConfig {
  RouterConfig::default()
    .with_default_head(head())
    .with_loader_url("/static/loader.min.js")
}

SolRoutes Variants

VariantDescription
PagePage route (HTML response)
GetGET API route (JSON response)
PostPOST API route (JSON response)
LayoutNested layout group
WithMiddlewareRoutes with middleware applied

Nested Layouts

Hierarchical layout structure support:

// Admin section layout
fn admin_layout(
  props : PageProps,
  content : ServerNode,
) -> ServerNode raise {
  ServerNode::sync(@luna.fragment([
    h1([text("Admin Panel")]),
    nav([
      a(href="/admin", attrs=[("sol-link", @luna.attr_static(""))], [text("Dashboard")]),
      a(href="/admin/users", attrs=[("sol-link", @luna.attr_static(""))], [text("Users")]),
    ]),
    div(class="admin-content", [content.to_vnode()]),
  ]))
}

// Route definition
SolRoutes::Layout(
  segment="admin",     // URL prefix
  layout=admin_layout, // Layout function
  children=[
    SolRoutes::Page(path="/admin", handler=PageHandler(admin_dashboard), title="Admin"),
    SolRoutes::Page(path="/admin/users", handler=PageHandler(admin_users), title="Users"),
  ],
)

Middleware

Railway Oriented Programming based middleware system.

Basic Usage

// Compose middleware
let middleware = @middleware.logger()
  .then(@middleware.cors())
  .then(@middleware.security_headers())

// Apply to routes
SolRoutes::WithMiddleware(
  middleware=[middleware],
  children=[...],
)

Built-in Middleware

MiddlewareDescription
logger()Request logging
cors()CORS headers
csrf()CSRF protection
security_headers()Security headers
nosniff()X-Content-Type-Options
frame_options(value)X-Frame-Options

CORS Configuration

@middleware.cors_with_config(
  CorsConfig::default()
    .with_origin_single("https://example.com")
    .with_methods(["GET", "POST"])
    .with_credentials()
)

Security Headers

@middleware.security_headers_with_config(
  SecurityHeadersConfig::default()
    .with_csp("default-src 'self'")
    .with_frame_options("DENY")
)

Middleware Composition

// Sequential execution (m1 -> m2)
let combined = @middleware.then_(m1, m2)
// or
let combined = m1.then(m2)

// Compose from array
let pipeline = @middleware.pipeline([m1, m2, m3])

// Conditional execution
let conditional = @middleware.when(
  fn(ctx) { ctx.request.method == "POST" },
  csrf_middleware,
)

Server Actions

CSRF-protected server-side functions.

Basic Usage

// Define action handler
let submit_handler = ActionHandler(async fn(ctx) {
  let body = ctx.body
  // ... process
  ActionResult::ok(@js.any({ "success": true }))
})

// Register in registry
pub fn action_registry() -> ActionRegistry {
  ActionRegistry::new(allowed_origins=["http://localhost:3000"])
    .register(ActionDef::new("submit-form", submit_handler))
}

ActionResult Types

TypeDescription
Success(data)Success, return JSON data
Redirect(url)Success, redirect
ClientError(status, msg)Client error (4xx)
ServerError(msg)Server error (5xx)

Island Architecture

Island Component

Islands are components shared between SSR and client:

// app/client/counter/counter.mbt

pub fn counter(count : Signal[Int]) -> @luna.Node[CounterAction] {
  div(class="counter", [
    span(class="count-display", [text_signal(count)]),
    button(onclick=@luna.action(Increment), [text("+")]),
    button(onclick=@luna.action(Decrement), [text("-")]),
  ])
}

Hydration Triggers

TriggerDescription
LoadImmediate on page load
IdleOn requestIdleCallback
VisibleOn IntersectionObserver detection
Media(query)On media query match
NoneManual trigger

CSR Navigation

Links with sol-link attribute are handled as CSR:

a(href="/about", attrs=[("sol-link", @luna.attr_static(""))], [text("About")])

Click behavior:

  1. sol-nav.js intercepts click

  2. fetch retrieves new page HTML

  3. Replace <main id="main-content"> content

  4. Update browser history with History API

  5. Hydrate new page Islands

Streaming SSR

Async content streaming:

@luna.vasync(async fn() {
  let data = fetch_data().await
  div([text(data)])
})

SSG Mode

Sol automatically detects SSG mode when your project has sol.config.json with ssg or docs section.

# Create SSG project
sol new my-docs --ssg

# Dev/build commands work the same
sol dev    # Starts SSG dev server with HMR
sol build  # Generates static site
sol lint   # Lints SSG content

For SSG-specific features and configuration, see Sol SSG.

See Also