loco-rs/loco · error

ServerOnly boot always builds a router

Error message

ServerOnly boot always builds a router

What it means

The generated AWS Lambda handler boots the app with StartMode::ServerOnly and then unwraps boot.router with an expect stating 'ServerOnly boot always builds a router'. ServerOnly mode is supposed to always construct the router, so a None here is an internal invariant violation — either the boot failed partially, the app's Hooks::routes returned nothing, or the generated code was edited/uses an incompatible loco version where ServerOnly no longer guarantees a router.

Solutions

  1. Verify loco_rs version matches the generated template's expectation (cargo loco doctor; compare Cargo.lock) and regenerate the deployment with `cargo loco generate deployment`.
  2. Ensure your Hooks::routes implementation returns a valid Routes and app boot is unmodified for ServerOnly mode.
  3. Inspect create_app return: if boot.router can legitimately be None in your version, handle it explicitly instead of unwrapping.
  4. Diff your lambda handler against a freshly generated template to spot accidental edits to the boot sequence.
  5. Enable/configure the server feature so ServerOnly boot actually constructs the router.

Example fix

// before
let router = boot
    .router
    .expect("ServerOnly boot always builds a router");
// after
let router = boot.router.ok_or_else(|| {
    anyhow::anyhow!("loco ServerOnly boot did not build a router; check Hooks::routes and loco version")
})?;
Defensive patterns

Strategy: type-guard

Validate before calling

// before unwrap
debug_assert!(boot.router.is_some(), "ServerOnly boot must build a router; check loco version and Hooks::routes");

Type guard

fn require_router(boot: Boot) -> axum::Router {
    match boot.router {
        Some(r) => r,
        None => panic!("ServerOnly boot returned no router — verify loco_rs version and Hooks::routes"),
    }
}

Try / catch

let router = match boot.router {
    Some(r) => r,
    None => {
        eprintln!("lambda boot: no router built; check loco version and Hooks::routes");
        return Err(anyhow::anyhow!("no router built during ServerOnly boot"));
    }
};

Prevention

When it happens

Trigger: Deploying the lambda template and having create_app::<App>(StartMode::ServerOnly, ...) return a boot struct whose .router is None — e.g. hooks misconfigured so no router is built, a loco version mismatch where the ServerOnly contract changed, or hand-edited generated main/lambda code that altered the boot path.

Common situations: Pinning a newer/older loco_rs than the template expects; customizing Hooks::routes or boot logic and accidentally skipping router construction; copy-pasting the lambda template into an app whose boot differs from the scaffolded one; feature flags (e.g. no server feature) changing create_app behavior.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of loco-rs/loco@23639d1e36 (2026-09-12). Data as JSON: /api/errors/295ba1241848702a. Report an issue: GitHub.

Appendix: source

Thrown at loco-gen/src/templates/deployment/lambda/lambda.t:67

use loco_rs::boot::{create_app, StartMode};
use loco_rs::environment::{resolve_from_env, Environment};
{%- if db %}
use migration::Migrator;
{%- endif %}
use {{pkg_name}}::app::App;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let environment: Environment = resolve_from_env().into();
    let config = App::load_config(&environment).await?;
    {%- if db %}
    let boot = create_app::<App, Migrator>(StartMode::ServerOnly, &environment, config).await?;
    {%- else %}
    let boot = create_app::<App>(StartMode::ServerOnly, &environment, config).await?;
    {%- endif %}
    let router = boot
        .router
        .expect("ServerOnly boot always builds a router");
    run(router).await
}

View on GitHub (pinned to 23639d1e36)