Staylight is a small premium travel booking demo inspired by Booking.com — not a clone. It showcases a complete search-to-booking flow with seeded mock data, internal API routes, and Smart Match, a rule-based scoring engine that explains why each stay fits a trip.
Built with Next.js App Router, React, TypeScript, and Tailwind CSS.
Render: staylight.onrender.com
Guardrails that lay the foundation for production readiness: Why AI-Generated Code Actually Fails And How To Fix It
- Search & browse — filter stays by destination, dates, guests, budget, and trip type
- Smart Match results — each result includes a fit score, label, reasons, and tradeoffs
- Stay details — gallery, amenities, pricing, availability, and guest reviews
- Reviews — read existing reviews and submit new ones (in-memory for the session)
- Mock checkout — guest details, clearly labeled demo payment, and booking confirmation
- Responsive UI — tuned for mobile, tablet, and desktop breakpoints
- Async states — loading, empty, error, and success handling across search, detail, checkout, and confirmation flows
Smart Match is Staylight's differentiating feature. It lives in lib/smart-match/calculateSmartMatch.ts and is rule-based only — no AI, LLMs, or external APIs.
For each stay, the engine combines weighted signals into a score from 0–100:
| Signal | What it checks |
|---|---|
| Budget | Price per night vs. your stated budget (within, slightly over, or well above) |
| Location | Distance from city center (closer stays score higher) |
| Rating | Guest rating tiers (e.g. highly rated, well rated, lower ratings) |
| Amenities | Overlap between stay amenities and trip-type preferences |
| Cancellation | Whether free cancellation is available |
| Trip type | Maps trip type (business, family, budget, romantic, leisure) to preferred amenities |
Each result includes:
- A score (0–100)
- A label — Excellent match, Good match, or Fair match
- Reasons explaining positive fit
- Tradeoffs when a stay is not ideal
Search results are ranked by Smart Match score before being returned to the client.
| Layer | Location | Role |
|---|---|---|
| Pages & UI | app/, components/ |
App Router pages and reusable components |
| API routes | app/api/ |
Thin handlers — validate input, call services, return JSON |
| Services | lib/services/ |
Business logic for stays, search, bookings, reviews |
| Smart Match | lib/smart-match/ |
Pure scoring functions |
| Seed data | lib/data/ |
In-memory stay, review, and booking data |
| Utilities | lib/utils/ |
Dates, money, logging, booking storage helpers |
| Types | types/ |
Shared domain models |
Principles:
- Client components fetch via API routes — no direct seed imports in the browser
- Services never import React; prefer plain functions over classes — no repository pattern, DI, or ORM
- Use server components where data fetching is straightforward; client components for interactivity
- Consistent API errors:
{ error: string }with appropriate HTTP status - Request logging via
lib/utils/logger.ts
Staylight targets a premium minimalist aesthetic — warm off-white surfaces, generous spacing, large rounded corners, and restrained typography. Visual guidance lives in .cursor/rules/design-system.mdc.
| Decision | Choice | Why |
|---|---|---|
| Data | Seeded in-memory modules | Zero env vars, simple deploy, no database setup |
| Backend | Next.js API route handlers | Single codebase; services stay thin and testable |
| Differentiator | Rule-based Smart Match | Explainable fit scores without AI cost or complexity |
| Payments & auth | Mocked / omitted | Demo scope — full booking flow without real integrations |
| Maps & external APIs | Deferred by default | Keeps the app self-contained; see roadmap for a stylized map option |
| Method | Route | Description |
|---|---|---|
GET |
/api/stays |
List all stays (?featured=true for featured subset) |
GET |
/api/stays/[id] |
Stay detail with reviews |
GET |
/api/search |
Search stays with Smart Match ranking |
POST |
/api/bookings |
Create a mocked booking |
POST |
/api/reviews |
Submit a review for a stay |
destination, checkIn, checkOut, guests, budget (optional), tripType
Async flows across search, stay detail, checkout, and confirmation include explicit UI states:
- Loading — skeletons and spinners while data is fetched
- Empty — helpful messaging when no stays or reviews match
- Error — user-facing error copy with retry where appropriate
- Success — confirmation screens and inline success feedback after booking or review submission
- Semantic HTML landmarks and headings
- Keyboard-navigable forms, buttons, and interactive controls
- Visible focus states on interactive elements
- ARIA labels and live regions where needed (e.g. async status updates)
- Sufficient color contrast for text and interactive states
Tests use Vitest with Testing Library for component and service coverage.
Current coverage includes:
- Unit tests for
calculateSmartMatch(budget, location, rating, amenities, cancellation, trip type) - Service tests for search and booking logic
npm run test # run once (CI)
npm run test:watch # watch mode during developmentGitHub Actions runs on every push and pull request (.github/workflows/ci.yml):
- Checkout
- Setup Node 22 (npm cache keyed on
package-lock.json) npm ci- Restore Next.js build cache (
.next/cache) when source and lockfile are unchanged npm run lintnpm run typechecknpm run testnpm run build
Note: CI validates the project; it does not deploy. A concurrency group cancels redundant runs when the same branch or PR receives new commits quickly.
CI and Render build independently — green CI means Render’s npm run build step is very likely to succeed, but Render still runs its own install and build on deploy.
Requirements: Node.js 20+
npm install
npm run devOpen http://localhost:3000.
| Command | Description |
|---|---|
npm run dev |
Start development server |
npm run build |
Production build |
npm run start |
Serve production build |
npm run lint |
Run ESLint |
npm run typecheck |
Run TypeScript without emit |
npm run test |
Run tests once |
npm run test:watch |
Run tests in watch mode |
npm run lint
npm run typecheck
npm run test
npm run buildStaylight works on any Node host that runs npm run build then npm start. No environment variables are required — the app uses seeded mock data only.
The repo includes [render.yaml](render.yaml) and [.node-version](.node-version) (Node 22) so Render can pick up settings automatically.
- Push the repository to GitHub.
- Sign in to Render and click New → Blueprint (or New → Web Service).
- Connect the Staylight GitHub repository.
- If using Blueprint, Render reads
render.yaml:
- Build command:
npm ci && npm run build - Start command:
npm start - Node version: 22
- If configuring manually instead:
- Runtime: Node
- Build command:
npm ci && npm run build - Start command:
npm start - Node version: 22 (or rely on
.node-version)
- No environment variables are required.
- Click Deploy. Render rebuilds from source on each push to
main(independent of GitHub Actions).
After deployment, update the live demo link at the top of this README with your Render URL.
- Seeded data only — no real database; server-side data resets on restart
- Mocked payment — checkout collects demo card fields but never charges
- No authentication — no user accounts or login
- No real payment processing — Stripe and similar integrations are out of scope
- No external travel APIs — destinations and availability are mock content
- Session-scoped mutations — new reviews and bookings persist in memory; booking confirmation also uses browser session storage for the current session
- Basic gallery — stay detail uses a hero image and thumbnail strip; seed data has few photos per stay
- Browse mode is minimal —
/stayswithout search params lists all stays with no filter or sort controls - No map visualization — location is text and distance-from-center only
These items are documented for future work only — not yet implemented. Suggested priority order:
- Rich image gallery — Expand seed photos (outdoor, indoor, room categories); add lightbox, keyboard navigation, and a “View all photos” grid on stay detail. Listing cards would show richer cover imagery.
- Browse filters and sorting on
/stays— Filter by city or country; sort by popularity (rating and review count), price, or distance. Search results could also expose a user-selectable sort (Smart Match, price, rating). - Smart Match Explore — A lighter discovery flow for users who know budget, trip type, or occasion (e.g. anniversary, conference) but not a specific destination or dates. Rule-based recommendations only — an extension of Smart Match, not AI.
- Stylized location map — Add latitude/longitude to seed data and render a premium static neighborhood map on stay detail (and optionally search). Avoids external map API keys while still conveying where a stay sits relative to the city center.
Smaller enhancements that reuse existing logic: “Similar stays” on the detail page (same city, trip type, price band) and a search-results sort toggle.
app/ Pages and API routes
components/ UI, layout, search, stays, checkout
lib/data/ Seed data
lib/services/ Business logic
lib/smart-match/ Fit scoring engine
lib/utils/ Shared helpers
tests/ Vitest unit and service tests
types/ Shared TypeScript types
render.yaml Render Blueprint (deploy config)