
Available
Xenier is a quotes website we designed, built and run ourselves. Thousands of quotes can be searched by author, category, tag or idea, then listened to, saved, discussed and shared. Behind that simple experience is a full-stack product: a server-rendered Next.js application, a PostgreSQL data layer, an admin CMS, a native Android client, an agent-ready MCP interface and a containerised deployment pipeline. This is how we took it from design to production.

The brief: make a quote useful, not just memorable
Most quote sites are long lists of text optimised for page views. We wanted Xenier to help people find the right words for a moment and do something with them. That set the product goals early:
- Fast, precise search across a large collection, without forcing people through menus.
- Every quote is a proper page that ranks in search engines and previews well when shared.
- People can react, bookmark, comment and contribute their own quotes.
- Editorial guides turn quotes into practical reflection and next steps.
- One back end serves the website, the mobile app and AI assistants.
Design: a calm reading experience
We designed Xenier around the quote itself. Cards use a single accent rule, an italic serif for the quote and a quiet author line, so a grid of thousands of quotes still reads calmly. Readers can switch between grid and list views, light and dark themes, colour schemes and text sizes, and a global audio control keeps playback under their control. Design tokens defined once in CSS are shared with the mobile app, so both clients look like the same product.
Search is the front door. Rather than a wall of filters, the search box understands short prefixes, so a power user can narrow results in one line:
a:epictetus → quotes by Epictetus
t:attitude → quotes tagged "attitude"
c:wisdom kindness → "kindness" within the Wisdom category
Each quote page carries reactions, bookmarking, sharing and a "More quotes to explore" rail drawn from the same topic and author. That rail keeps readers moving through the collection and gives search engines a dense, meaningful internal link structure.
Architecture: ports and adapters on Next.js 15
Xenier began life as a single-page React app with a separate Express API. Shared links showed no previews because the server only ever returned the empty app shell. Rather than patch it, we re-platformed the whole product onto Next.js 15 with React 19 and TypeScript, server-rendering every quote, author, category and guide page, and moving the API into Next.js route handlers.
We designed the data layer as ports and adapters. The application depends only on typed interfaces, and a single composition root decides which implementation backs them:
// lib/storage.ts — the only file that knows which database is in use
import { storage as postgresStorage } from '@/lib/adapters/postgres';
import type { IStorage } from '@/lib/core';
export const storage: IStorage = postgresStorage;That seam paid off. When we moved Xenier off a hosted backend-as-a-service onto self-hosted PostgreSQL 16, the change was a new adapter and a one-line swap, not a rewrite of pages and API routes.
Data model and content management
Quotes, authors, categories, tags, reactions, comments, bookmarks and audio live in a versioned PostgreSQL schema managed by plain SQL migrations. Tags use native arrays with GIN indexes, so filtering by any combination of tags stays fast as the collection grows. When guides gained tags, we deliberately reused the same shape as quote tags, so one vocabulary covers the whole site:
ALTER TABLE articles ADD COLUMN IF NOT EXISTS tags TEXT[] NOT NULL DEFAULT '{}';
CREATE INDEX IF NOT EXISTS idx_articles_tags ON articles USING GIN(tags);Editors work in a purpose-built admin area: a shared data grid with search, filters by status, layout and tag, resizable columns and inline actions, plus an article editor with revision history and draft and publish workflows. Community submissions arrive unapproved and go through moderation before they appear publicly.

Search visibility built in
- Server-rendered pages with clean, readable slugs for quotes, authors and categories.
- A generated sitemap and robots policy, canonical URLs and structured metadata.
- Dynamic Open Graph images rendered on the server, so every shared quote shows a branded preview card.
- Editorial guides that answer real questions and link back into the collection.
One API, three kinds of client
The website, the Android app and AI assistants all use the same API. We built the Android client with React Native, Expo SDK 54 and Expo Router. It has Home, Explore, Daily, Saved and Profile tabs, guest browsing, secure token storage and the same reactions, audio and comments as the web. There is no mobile-only back end: the app consumes the typed API the website already uses.

For AI assistants, Xenier includes an MCP server modelled on the one we built for MotorLoop. It exposes curated, read-only tools such as search_quotes, get_quote and get_search_options over published content only. An OpenAPI document describes the full REST surface. Both sit behind fail-closed feature flags, so they are switched on deliberately rather than by accident:
{ "method": "tools/call",
"params": { "name": "search_quotes",
"arguments": { "category": "Wisdom", "author": "Epictetus" } } }Security and performance
- A strict Content Security Policy set in middleware, with each external origin explicitly allowed.
- Token-based authentication shared by web and mobile, with refresh handling.
- Rate limiting and bot detection to discourage bulk scraping of the collection.
- Waveform caching and background preloading so audio starts quickly.
- Privacy-respecting, self-hosted analytics with a consent-first privacy settings panel.
Testing and delivery
Every change goes through TypeScript checks, Vitest unit and integration tests and Playwright end-to-end tests before a production build. The application ships as a standalone Next.js build inside a Docker image, with PostgreSQL running alongside it on an internal network that is never exposed publicly. Secrets come from a central vault rather than files in the repository. Azure DevOps pipeline definitions cover continuous integration, UAT, production and the signed Android build, which is submitted to Google Play testing tracks with fastlane.
We run Xenier on infrastructure we operate, with separate UAT and production environments, each with its own database and media storage. Health checks and analytics tell us how the site is behaving after each release.
What Xenier shows about how we work
Xenier is a small idea delivered with the same discipline we bring to customer platforms: product design that starts from what people are trying to do, an architecture that can change its database without a rewrite, content and SEO treated as engineering problems, one API shared across web, mobile and AI, and a delivery pipeline we can trust. If you are planning a content-rich website, a community platform or an app that needs all three channels, we would be glad to talk.
