Skip to content

Performance

Tyravel ships snappy defaults for production: route caches, JSON fast paths, request pooling, compiled view LRU, and pool warm-up. This guide is the boot checklist and tuning reference for Tier 19 speed work.

Production boot checklist

Run these once per deploy (or in your CI image build):

bash
tyravel config:cache
tyravel route:cache
tyravel view:cache

Then boot with production env:

bash
NODE_ENV=production APP_DEBUG=false tyravel start

In your app entrypoint, call prepareHttpServer() after app.boot() so route and middleware caches are validated before accepting traffic.

What warms automatically

FeatureConfig / envDefault in production
Route cachestorage/framework/routes.jsonBuilt by route:cache
Config cachestorage/framework/config.jsonBuilt by config:cache
View compiled cacheview:cache + preloadCompiled: trueOn in config/views.ts scaffold
DB pool warm-upDB_POOL_WARMUP=trueOn when NODE_ENV=production
Request poolinghttp.requestPoolingOn when APP_DEBUG=false
JSON fast pathhttp.jsonFastPathOn
Early 404http.early404On when APP_DEBUG=false

Headless API mode

For JSON-only backends, use tyravel new --headless. Headless scaffolds skip views, SSR, and Echo — smaller boot profile and faster cold start. See Headless API.

HTTP hot path

  • JSON fast path — stateless API routes skip session, CSRF, and view middleware. Tag routes with withMiddlewareMeta(..., { tag: 'session' }) only when you need sessions.
  • Request pooling — reuses TyravelRequest instances under load. Enable with requestPooling: true in config/http.ts (default off in debug, on in production scaffolds).
  • Keep-alive — Node adapter sets keepAliveTimeout / headersTimeout for reverse-proxy deployments.

Views & SSR

  • view:cache — compile all .tyr templates to disk; production rejects cache misses when compiled: true.
  • Runtime LRU — compiled templates are cached in memory after first read.
  • Streaming shell flushResponse.ssrStream() and View.streamSsr() emit the document <head> (including CSS links) before the first view chunk. Pass head with your Vite or asset tags for fastest first paint.
  • Empty hydration skip — pages without @island markers omit the hydration JSON script entirely.
typescript
Route.get('/dashboard', () =>
  View.streamSsr('dashboard', context, handlers, {
    head: '<link rel="stylesheet" href="/build/app.css">',
  }),
);

For Turbo/HTMX partial updates without full reloads, see the partial reload cookbook.

ORM throughput

  • Model.select('id', 'title') — prune columns on wide tables instead of Model.all(); benchmark key orm.select.pruned
  • Prepared statements — SQLite caches prepare() per connection; Postgres uses named prepared SELECTs; MySQL caches in transactions
  • Model.remember() — cache expensive aggregate query results (see earlier section)
  • Model.insertMany() — batch inserts for seeders

Database & pools

SQLite (file-backed)

Production scaffolds enable WAL journal mode by default (journalMode: 'wal' in config/database.ts). WAL allows concurrent readers while a writer is active — important for SQLite on Fly/Railway with multiple workers.

Postgres / MySQL pool sizing

Start with one pool per worker process, not one global pool for the whole machine:

DeploymentSuggested DB_POOL_MAX per workerNotes
Fly.io (1 shared CPU)5–10Match tyravel start worker count
Railway (512 MB)5Watch connection limits on hobby Postgres
Single VPS (2 workers)10 per workerTotal ≤ provider max connections

Environment variables (driver packages):

  • DB_POOL_WARMUP — fire SELECT 1 on boot (default true in production scaffold)
  • DB_POOL_MAX — max connections per pool
  • DB_POOL_IDLE_TIMEOUT — idle connection eviction in ms

Enable pool warm-up so the first real query does not pay connection setup latency.

Caching

Scaffold config/cache.ts defaults to the file driver. In production with Redis available:

bash
CACHE_STORE=redis

Wrap expensive reads:

typescript
const posts = await Cache.remember('home:posts', 300, async () => {
  return Post.query().orderBy('published_at', 'desc').limit(10).getModels();
});

For safe public GET routes, add HTTP cache middleware (ETag / 304) from Tier 15. See Cache.

When to use Redis

Use Redis when…Skip Redis when…
Multiple app workers share sessionsSingle-process dev
Queue driver is redisDatabase queue is enough
Cache hit rate matters at scaleFile cache is fine for low traffic
WebSocket broadcast fan-outLog driver suffices

Anti-patterns

  • N+1 queries — use with() / eager loading; enable Model.preventLazyLoading() in development.
  • Uncached views in production — always run view:cache before deploy; set compiled: true.
  • Session on every API route — use JSON fast path; scope session middleware to browser routes only.
  • Synchronous boot work — defer admin, debug, and MCP providers (lazy registration) until first use.
  • Full page reload for small UI updates — use View.partial() / Response.partial() instead.

HTTP/2 and clustering

HTTP/2 (Node adapter, TLS required):

typescript
await serve(kernel, {
  port: 3000,
  tls: { certPath: 'storage/certs/local.pem', keyPath: 'storage/certs/local-key.pem' },
  http2: true,
});

Or set TYRAVEL_HTTP2=1 with TYRAVEL_TLS_CERT / TYRAVEL_TLS_KEY.

Multiple workerstyravel start --cluster forks node:cluster workers (default: CPU count). Use Redis or database sessions so requests are not pinned to a single process:

bash
tyravel start --cluster --workers=4

Single-file bundle

For edge deploys (Fly Machines, Lambda-style), bundle the entry after caches are warm:

bash
tyravel config:cache && tyravel route:cache && tyravel view:cache
npm install -D esbuild
tyravel build --outfile=bootstrap/app.mjs --minify
node bootstrap/app.mjs

Trade-offs: faster cold start, but native addons and some dynamic imports may need esbuild plugins. See bootstrap/README.txt after build.

Perf budgets in CI

Add thresholds to tyravel.json:

json
{
  "perf": {
    "budgets": {
      "http.json": { "min": 500, "unit": "req/s" },
      "boot.cold": { "max": 400, "unit": "ms" }
    }
  }
}

Run tyravel test --perf to fail when benchmarks regress (requires scripts/benchmark.mjs in the monorepo or linked checkout).

Measuring

bash
npm run benchmark
BENCHMARK_QUICK=1 npm run benchmark -- --json

See Benchmarks for CI snapshots, regression gates, and Bun vs Node notes.

Released under the MIT License.