Loam — MTB Build & Tune

A Next.js web app for mountain bike riders to configure builds, get manufacturer-verified suspension and tire setup starting points, save post-ride reviews, and receive AI-powered tuning recommendations based on personal preference history.

Built with Next.js App Router, Prisma/PostgreSQL, and Ollama for local LLM inference. The API layer is designed for reuse in a future React Native/Android client.


Tech stack

Layer Tech
Framework Next.js 16 (App Router)
Language TypeScript
Database PostgreSQL
ORM Prisma
Auth bcryptjs + jose (JWT in HTTP-only cookies)
Images sharp (layer compositing)
AI Ollama (local LLM)
Validation Zod

Features

  • Catalog — Curated frames, forks, shocks, and tires with real-world setup ranges
  • Setup verification — Every product carries a research audit trail (manufacturer docs, community sources, confidence levels)
  • Build composer — Layered bike image generation from selected components
  • Per-user learning — Builds, reviews, and learned preferences are scoped to authenticated users
  • AI recommendations — Ollama-powered tuning advice that incorporates the rider's own review history
  • Post-ride reviews — Rate suspension feel, grip, rolling speed, and report issues (bottom-outs, harsh hits, wallow, small-bump harshness)

Getting started

Prerequisites

  • Node.js 20+
  • PostgreSQL running locally (or a remote instance)
  • Ollama installed and running (optional, for AI recommendations)

1. Install dependencies

npm install

2. Configure environment variables

cp .env.example .env.local

Edit .env.local:

DATABASE_URL=postgresql://user:password@localhost:5432/loam
JWT_SECRET=change-this-to-a-long-random-string-in-production

# Optional — defaults shown
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b

3. Set up the database

# Create and apply migrations
npm run db:migrate

# Seed product catalog
npm run db:seed

4. Pull an Ollama model (optional)

ollama pull llama3.1:8b

5. Run the dev server

npm run dev

Open http://localhost:3000.


Project structure

prisma/
  schema.prisma      # User, BikeBuild, RideReview, RiderPreference, Product
  seed.ts            # Seeds catalog into Product table
src/
  app/
    api/
      auth/          # register, login, me, logout
      builds/        # list / create builds (scoped to user)
      reviews/       # create reviews (scoped to user + build ownership)
      ai/recommend   # Ollama recommendation prompt
      images/compose # sharp layer compositing
      catalog/       # public catalog + setupVerification
      setup-audit/   # research audit endpoint
  components/
    BikeBuilderApp.tsx
  lib/
    auth.ts          # bcrypt, JWT cookie session, requireAuth
    db.ts            # per-user Prisma queries
    catalog.ts       # static product data with citations
    recommendations.ts # setup math by rider profile
    setupVerification.ts # research audit metadata
    types.ts         # shared TypeScript types
public/
  images/            # product assets + user-generated builds

API routes

Route Method Auth Description
/api/auth/register POST No Create account, set session cookie
/api/auth/login POST No Log in, set session cookie
/api/auth/me GET No Return current session user
/api/auth/logout POST No Clear session cookie
/api/builds GET Yes List current user's builds
/api/builds POST Yes Save a new build
/api/reviews POST Yes Review a build (must own it)
/api/ai/recommend POST Yes AI tuning advice with user preferences
/api/images/compose POST No Compose build image from layers
/api/catalog GET No Full catalog + setupVerification
/api/setup-audit GET No Research audit report

Auth system

  • Passwords hashed with bcrypt (12 rounds)
  • Sessions are JWTs stored in HTTP-only loam-session cookies (7-day expiry)
  • requireAuth() guards data-modifying routes
  • All builds, reviews, and preferences are scoped to the authenticated userId

Data model overview

  • User — email (unique), passwordHash, name
  • BikeBuild — belongs to User; stores component IDs, rider profile, setup JSON, image path
  • RideReview — belongs to User + BikeBuild; ratings and issue flags
  • RiderPreference — one per User; aggregated from review history (harsh/soft tendency, grip preference)
  • Product — seeded catalog of frames, forks, shocks, tires with specs and sources JSON

Research & citations

Every product entry includes a sources array and a SetupVerification record:

  • manufacturer — official setup guides, owner manuals, product pages
  • community — Pinkbike reviews, Reddit discussions, Blister/MTBR test notes
  • Status levels: verifiedmanufacturer-guidedcommunity-informedestimated

The /api/setup-audit endpoint returns a full breakdown of confidence levels and missing sources.


Notes on Android reuse

The API surface is plain JSON over HTTP with cookie-based auth. A React Native or Android client can reuse the same endpoints by:

  1. Implementing cookie jar storage for the loam-session cookie
  2. Calling /api/auth/login or /api/auth/register to obtain a session
  3. Reusing /api/builds, /api/reviews, /api/ai/recommend, and /api/catalog as-is

The image composition endpoint (/api/images/compose) returns a PNG path; clients can either request server-side composition or replicate the layer-stacking logic locally.


Scripts

Command Purpose
npm run dev Start Next.js dev server
npm run build Production build
npm run db:migrate Run Prisma migrations
npm run db:seed Seed product catalog
npm run db:studio Open Prisma Studio
npm run lint Run ESLint

Environment variables

Variable Required Default Description
DATABASE_URL Yes PostgreSQL connection string
JWT_SECRET Yes Secret for signing session JWTs
OLLAMA_URL No http://localhost:11434 Ollama server base URL
OLLAMA_MODEL No llama3.1:8b Model name for recommendations
S
Description
No description provided
Readme 7.1 MiB
Languages
Kotlin 58.7%
TypeScript 38.6%
CSS 2.1%
Dockerfile 0.5%
JavaScript 0.1%