This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ZipURL is a URL shortening service with UTM parameter tracking, Open Graph metadata support, and OIDC authentication. Built with Rust using the Leptos full-stack framework (version 0.8) with server-side rendering (SSR) and client-side hydration.
This project uses Nix flakes for reproducible development environments. All development should be done within the Nix shell:
nix develop # Enter the dev environment (automatic with direnv)The Nix shell provides:
- Rust nightly toolchain with wasm32-unknown-unknown target
- cargo-leptos (must be installed separately:
cargo install cargo-leptos) - just (task runner)
- Tailwind CSS v4
Development:
just dev # Start dev server with hot reload (port 1337)
cargo leptos watch # Same as above (direct command)
just dev-server # Backend only (no SSR)Building:
just build # Development build
just build-release # Production build with optimizations
cargo leptos build # Full Leptos build (frontend + backend)Testing:
just test # Run all tests
just test-one <TEST> # Run specific test
just test-unit # Unit tests only
just test-integration # Integration tests onlyCode Quality:
just fmt # Format code (includes leptosfmt)
just lint # Run clippy
cargo clippy -- -W clippy::all # Clippy for server
cargo clippy --target wasm32-unknown-unknown -- -W clippy::all # Clippy for WASMFrontend Checks:
just check-frontend # Verify WASM compilation
just check-all # Check both frontend and backendThe workspace is organized into 4 crates with clear separation of concerns:
-
zipurl-core - Shared data models and DTOs
- Domain models:
ZipUrl,DomainConfig,ApiKey,Tag - Metadata types:
CardMetadata,UtmParams,OgType,TwitterCard - No dependencies on server or client code
- Used by all other crates
- Domain models:
-
zipurl-server - Backend logic and API
- Database layer:
db.rsuses redb (embedded database) - API routes:
api/directory (domains, urls, tags, redirect, metadata) - Authentication:
auth/directory (OIDC/Keycloak integration) - Metadata fetching:
metadata.rs(scrapes Open Graph tags) - Bot detection:
bot_detection.rs - Metrics: Prometheus metrics via
metrics.rs
- Database layer:
-
zipurl-client - Browser-side utilities
- WASM-specific code for browser interactions
- Uses gloo-* crates for web APIs
-
zipurl-app - Leptos UI application
- Main entry point:
main.rs(SSR setup with Axum) - App root:
app.rs(Leptos component tree) - Pages:
pages/directory - Reusable components:
components/directory - Modals:
modals/directory - Feature flags:
ssrfor server,hydratefor client
- Main entry point:
ZipURL uses Leptos with full-stack SSR:
- Server side (
ssrfeature): Renders initial HTML, handles API routes via Axum - Client side (
hydratefeature): Takes over after initial load, compiles to WASM - Shared code: Components in
zipurl-appcompile for both targets
The main.rs sets up:
- Axum router with Leptos routes
- Database connection (redb)
- Optional OIDC auth provider
- API routes merged with SSR routes
- Static file serving from
target/site
- Type: redb (embedded key-value database)
- Location:
zipurl.db(configurable via config.toml) - Schema: See
zipurl-server/src/db.rsfor table definitions - Key tables:
urls- Short URL recordsdomains- Domain configurationsapi_keys- API key hashestags- Tag definitionsurl_tags- Many-to-many URL-tag relationships
Configuration is loaded via figment (supports TOML files + env vars):
config.toml- Main config file (not in git)config.toml.example- Template to copy- Environment variables override file settings
- See
zipurl-server/src/config.rsfor structure
Key settings:
database_path- Database file locationhost,port- Server bindingbase_url- Public URL for short linksauth.mode- "none", "mock", "oidc", or "keycloak"
The frontend compiles to WASM and has different clippy requirements:
# Check frontend compilation
cargo check --package zipurl-app --target wasm32-unknown-unknown --no-default-features --features hydrate
# Run clippy for frontend
cargo clippy --package zipurl-app --target wasm32-unknown-unknown --no-default-features --features hydrateBackend code uses the ssr feature:
# Check backend compilation
cargo check --package zipurl-server
# Run backend only (useful for API development)
cargo run --package zipurl-serverUse cargo add (provided by nix shell):
cargo add <crate> # Add to workspace
cargo add -p zipurl-server <crate> # Add to specific crateFor workspace-shared deps, add to [workspace.dependencies] in root Cargo.toml.
just db-backup # Backup database
just db-reset # Delete and recreate databasejust keycloak-up # Start Keycloak in Docker
just keycloak-down # Stop Keycloak
just keycloak-logs # View logs
just keycloak-reset # Reset Keycloak dataTailwind v4 is integrated via cargo-leptos:
- Input file:
crates/zipurl-app/style/tailwind.css - Automatically compiled during
cargo leptos watch - Output: Included in build artifacts
Always use feature flags for platform-specific code:
#[cfg(feature = "ssr")]
// Server-only code
#[cfg(feature = "hydrate")]
// Client-only code- Server errors: Use
zipurl_server::error::ServerError(thiserror-based) - Core errors: Define in
zipurl-coreif shared - Leptos server functions: Return
Result<T, ServerFnError>
- Use the
#[component]macro - Props should derive
Clonefor reactivity - Server functions use
#[server]macro - Keep component files focused and small
- Co-locate with code using
#[cfg(test)] - Use
rstestfor parameterized tests - Mock external dependencies with
mockall
- Located in
tests/directory - Use
wiremockfor HTTP mocking - Test full API flows
The project was recently rebranded from "ShortURL" to "ZipURL". Some references to the old name may still exist in documentation or comments.
Two custom release profiles are defined:
wasm-release- Optimized for WASM size (opt-level='z', LTO enabled)server-release- Optimized for server performance (opt-level=3, LTO enabled)
These are automatically used by cargo-leptos during release builds.
- 1337 - Main application server
- 1338 - Auto-reload WebSocket (dev mode)
- 8080 - Keycloak (when running via docker-compose)
- Never commit
config.toml,*.db, or API keys - The project uses Rust edition 2024
- WASM compilation requires the
getrandomcrate withjsfeature - All timestamps are
i64Unix timestamps (supports dates before 1970) - API keys are stored as SHA-256 hashes, never plaintext
- URL metadata can be auto-fetched from target URLs using Open Graph tags