⚡ zeno-rs
Your Laravel Blade Templates. Now Running in Rust.
You already know
@if,@foreach,@extends,{{ $var }}, and<x-component>.
You don't need to learn a new template language. You need a faster runtime.
Why Switch? · vs Tera · Quickstart · Blade Reference · Components · Hot Reload
🤔 Why Leave PHP?
You love Laravel. The DX is excellent, the ecosystem is mature, and Blade is genuinely good.
But at some point, every Laravel project hits the same wall:
| Problem | PHP/Laravel | zeno-rs (Rust) |
|---|---|---|
| Memory per request | ~20–50 MB (FPM workers) | ~2–5 MB (single binary) |
| Cold start | Opcache warm-up required | Instant — binary is pre-compiled |
| Concurrency | Process-per-request (FPM) or Swoole | Native async with Tokio / Axum |
| Deployment | PHP runtime + Composer + env | Single static binary, zero deps |
| Template syntax | Laravel Blade | Identical Blade syntax ✅ |
The catch with every other Rust web framework: you have to throw away your templates.
Tera, Handlebars, MiniJinja — none of them speak Blade.
zeno-rs does. Your .blade.zl files work as-is.
🆚 zeno-blade vs Tera — Why Blade Wins
Tera is the most popular Rust template engine. It's solid, well-documented, and widely used.
But if you're a Laravel developer — or if you care about developer experience — it falls short in ways that matter every day.
The Hot Reload Problem (This Is the Big One)
Here's what your workflow looks like when you change a template:
With Tera:
// Option A: Restart the server every time.
// Option B: Call full_reload() — which re-reads and re-parses EVERY template.
tera.full_reload?; // ← nukes the entire cache, re-parses all files
Tera has no per-file invalidation. Change one file → invalidate everything → re-parse everything.
On a project with 50+ templates, this adds latency to every dev refresh.
With zeno-blade:
Edit one template → Save → Refresh browser
✅ Only that one file is re-parsed (mtime check = 1 syscall)
✅ Every other template stays in RAM untouched
✅ Zero manual reload call needed
✅ Zero restart needed
zeno-blade uses mtime-based per-file cache invalidation:
check the file's last-modified timestamp on every request, re-parse only when it changes.
It's the best of both worlds — RAM speed when nothing changed, instant pickup when you saved.
Full Feature Comparison
| Feature | zeno-blade | Tera | Notes |
|---|---|---|---|
| 🔥 Hot reload — auto, per-file | ✅ | ❌ | Tera: call full_reload() to nuke entire cache |
| 🎨 Laravel Blade syntax | ✅ | ❌ | Tera uses Jinja2 / Django-like syntax |
| 🧩 HTML components () | ✅ | ❌ | Tera has no component system |
| 📐 Layout inheritance (@extends) | ✅ | ✅ | Both support @extends / @section / @yield |
| 🔁 Loop with empty fallback (@forelse) | ✅ | ❌ | No forelse equivalent in Tera |
| 🎯 Conditional CSS classes (@class) | ✅ | ❌ | Laravel-style @class directive |
| 🔐 Form helpers (@csrf, @method) | ✅ | ❌ | Tera has no form helpers |
| 🧠 Embedded scripting (ZenoLang) | ✅ | ❌ | Full scripting runtime built-in |
| 🔌 Custom handler / slot system | ✅ | ❌ | Register Rust functions callable from templates |
| 📄 Built-in OpenAPI / Swagger UI | ✅ | ❌ | Bundled in the zeno-rs workspace |
| 🛡️ Zero unsafe code in core | ✅ | ✅ | Both are memory-safe |
| 📦 Maturity / ecosystem | 🆕 | ✅ | Tera has a larger community — honest trade-off |
Syntax: What You Already Know vs What You'd Have to Learn
<!-- Tera — Jinja2-style, new syntax to learn -->
{% for post in posts %}
{% if post.featured %}
{{ post.title | upper }}
{% endif %}
{% else %}
No posts.
{% endfor %}
{{-- zeno-blade — Laravel Blade, you already know this --}}
@forelse($posts as $post)
@if($post_featured)
{{ $post }}
@endif
@empty
No posts.
@endforelse
If you've written a single Laravel view, you already know how to write zeno-blade templates.
[!NOTE] Tera is an excellent library and the right choice if you're not coming from a Blade background.
If you are — zeno-blade gives you Laravel's template DX at Rust's performance level.
🔥 What Exactly Is This?
zeno-rs is a Rust workspace (monorepo) containing:
zeno-rs/
├── crates/
│ ├── zenocore/ # 🔩 Core engine: lexer, parser, executor, scope — zero dependencies
│ ├── zeno-blade/ # 🎨 THE Blade engine — transpiles .blade.zl → AST → HTML
│ ├── zeno-std/ # 🧰 Standard library: math, date, string, money
│ ├── zeno-apidoc/ # 📄 OpenAPI 3.0 spec + Swagger UI
│ └── zenoengine/ # 📦 Batteries-included facade (start here)
└── examples/
└── web_server/ # 🚀 Full Axum web server, ready to run
zeno-bladeis the star of the show — a full Blade engine living insidezeno-rs.
It is the Rust sibling ofnextcore/zeno-go, the original Go implementation.
Templates are 100% portable between Go and Rust backends.
⚡ 2-Minute Migration
Step 1 — Add to Cargo.toml
[]
= "0.1" # batteries-included facade
= "0.1" # or just the Blade engine, if you don't need the full stack
All crates are published on crates.io. No git URLs needed.
Step 2 — Point it at your existing views directory
use Mutex;
use ;
use ;
use parse_string;
let mut engine = new_engine;
register_blade_slots;
let mut ctx = new;
ctx.set;
let scope = new;
scope.set; // 👈 same path
scope.set;
scope.set;
let node = parse_string.unwrap;
engine.execute.unwrap;
let html = ctx..unwrap;
println!; // ← your rendered HTML
Step 3 — Your existing Blade templates work unchanged
{{-- resources/views/dashboard.blade.zl — no changes needed --}}
@extends('layouts.app')
@section('content')
Welcome, {{ $user }}!
@if($role == 'admin')
Admin
@endif
@forelse($posts as $post)
{{ $post }}
@empty
No posts yet.
@endforelse
@endsection
That's it. No rewrite. No new syntax. Just a faster runtime.
🎨 Blade Directives
zeno-bladetranspiles.blade.zltemplates to ZenoLang AST nodes, then executes them against thezenocoreengine. The result is standard HTML — same as what Laravel would produce.
Full directive support, identical to Laravel Blade:
@extends('layouts.app')
@section('content')
Welcome, {{ $user }}!
{{-- Comments never appear in output --}}
@if($role == 'admin')
Admin
@elseif($role == 'moderator')
Mod
@else
User
@endif
@forelse($posts as $post)
{{ $post }}
@empty
No posts yet. Start writing!
@endforelse
@csrf
@method('PUT')
Save
@endsection
@push('scripts')
@endpush
Directive Reference
| Directive | Laravel Blade | zeno-blade |
|---|---|---|
{{ $var }} — escaped echo |
✅ | ✅ |
{!! $raw !!} — raw echo |
✅ | ✅ |
@if / @elseif / @else / @endif |
✅ | ✅ |
@foreach / @endforeach |
✅ | ✅ |
@forelse / @empty / @endforelse |
✅ | ✅ |
@extends('layout') |
✅ | ✅ |
@section / @endsection |
✅ | ✅ |
@yield('name') |
✅ | ✅ |
@include('partial') |
✅ | ✅ |
@push('stack') / @stack('stack') |
✅ | ✅ |
@class(['cls' => $cond]) |
✅ | ✅ |
@method('PUT') |
✅ | ✅ |
@csrf |
✅ | ✅ |
{{-- comment --}} |
✅ | ✅ |
🧩 HTML Components
Identical to Laravel Blade components — <x-component> with named slots and dynamic props.
Define once — resources/views/components/alert.blade.zl:
$is_danger, 'alert-success' => $is_success])>
{{ $header }}
{{ $slot }}
Use anywhere — same syntax as Laravel:
Access Denied
You don't have permission to view this page.
Output:
Access Denied
You don't have permission to view this page.
Props are automatically isolated — each component gets its own scope. No variable pollution.
⚙️ Template Loading & Hot Reload
[!IMPORTANT] Hot reload is the #1 reason to choose
zeno-bladeover Tera.
See the full comparison for details.
Most Rust template engines force a painful choice: either restart the server, or reload everything from scratch. zeno-blade does neither.
zeno-blade uses a smart mtime-based per-file cache:
- Template loads → parsed to AST, stored in RAM. ⚡
- Next request → check file's
modified time(one lightweight syscall, no file read). - File unchanged → serve AST straight from RAM. Zero disk I/O.
- File changed → re-read, re-parse, update cache automatically.
Edit template → Save → Refresh browser ✅ (changes visible instantly)
No edits → Every subsequent request ✅ (served from RAM, no disk touch)
No env vars. No restart. No cargo build. Works out of the box.
[!TIP] Recompiling Rust is only needed when you change Rust code (handlers, slots, business logic).
Template changes — layouts, components, partials — are always hot-reloaded automatically.
Preload mode (strict production)
If you want the server to fail at startup rather than at runtime when a template is missing:
use transpile_blade_native;
// In main() — warm up the entire cache before accepting requests
let views = ;
for view in &views
// All templates pre-loaded. Server ready.
🧰 ZenoLang — The Logic Layer
Beyond Blade, zeno-rs includes ZenoLang — a readable, indented scripting language that powers the execution layer. You won't write it in templates directly (Blade directives handle that), but it's available for server-side scripts and custom logic:
# Variables & types
set: $name = "Andi"
set: $score = 95
set: $tags = ['rust', 'fast', 'safe']
# Conditionals
if: $score >= 90 {
then:
elseif: $score >= 80 { set: $grade = "B" }
else:
}
# Loops
for: $tags {
as: $tag
do:
}
# Functions
fn: add {
params:
do:
}
# Error handling
try {
do:
catch:
}
🔌 Custom Slots (Extend the Engine)
Register your own handlers in Rust and call them from any template or script — like Laravel's custom Blade directives, but with the full power of Rust:
use Arc;
use ;
Then call it from a template or script:
db.find: 'users'
log: $result # → Queried users
🚀 Axum Example
A full Axum web server with Blade rendering, ZenoLang execution, and Swagger UI — clone and run:
🚀 ZenoEngine Axum server running at http://127.0.0.1:3000
📖 Swagger UI at http://127.0.0.1:3000/docs
| Method | Path | Description |
|---|---|---|
POST |
/execute |
Execute a ZenoLang script |
GET |
/docs |
Swagger UI |
GET |
/openapi.json |
OpenAPI 3.0 spec |
📄 OpenAPI / Swagger (Bonus)
Auto-generate API docs from your routes with zero config — something you'd need a separate package for in Laravel:
use ;
let registry = global;
registry.register;
// GET /openapi.json → full OpenAPI 3.0 spec
// GET /docs → interactive Swagger UI
🏗️ Build & Test
# Build all crates
# Run all tests
# Run only Blade engine tests
Requirements: Rust 1.85+ (Edition 2024)
🔗 Ecosystem
| Repository | Language | Description |
|---|---|---|
| nextcore/zeno-go | Go | Original ZenoEngine — Go implementation |
| nextcore/zeno-rs | Rust | This repository — Rust port |
Templates written for zeno-go are 100% compatible with zeno-rs.
Same .blade.zl files. Same directives. Same component syntax. Different runtime.
📝 License
Apache 2.0 © NextCore
Keep your Blade templates. Ditch the PHP overhead. Ship in Rust.
zeno-rsis the workspace —zeno-bladeis the Blade engine inside it.
⭐ If this saves you a rewrite, give it a star!