Component map
flowchart LR
Browser["Client browser"] -->|"HTTPS"| Traefik["Traefik ingress"]
Traefik --> Varnish["Varnish cache\n(static + /api/images/*)"]
Varnish --> Next["Next.js App Router\nSSR/ISR + API routes"]
Next --> Auth["Auth/session module\n(JWT cookie)"]
Next --> Prisma["Prisma ORM"]
Prisma --> Postgres[(PostgreSQL\nlistings + images)]
Next --> Mailer["SMTP mailer\nsmtp.lomavuokraus.fi (CNAME) + DKIM"]
Admin["Admins & moderators"] --> Traefik
Edit the Mermaid block above to evolve the architecture.
Domain model
erDiagram
USER ||--o{ LISTING : owns
USER ||--o{ LISTING : approves
LISTING ||--|{ LISTINGTRANSLATION : has
LISTING ||--o{ LISTINGIMAGE : has
USER {
string id
string email
string passwordHash
string role
string status
datetime emailVerifiedAt
datetime approvedAt
datetime rejectedAt
datetime removedAt
}
LISTING {
string id
string status
datetime approvedAt
datetime rejectedAt
datetime removedAt
string country
string region
string city
float latitude
float longitude
}
LISTINGTRANSLATION {
string id
string slug
string title
string locale
string teaser
}
LISTINGIMAGE {
string id
string url
string data
string mimeType
int size
string altText
boolean isCover
int order
}
Key notes
- Web: Next.js app (App Router), server-rendered pages, client hooks for auth state.
- API routes: Authentication, admin approvals, listings CRUD (soft-delete), profile update.
-
Data: Postgres via Prisma (models: User, Listing,
ListingTranslation, ListingImage, VerificationToken); listing images
stored as bytes + metadata and served through
/api/images/:id. -
Caching: Varnish sidecar caches
/api/images/*(24h) and/_next/staticassets (7d) before requests reach Next.js. - Mail: SMTP (smtp.lomavuokraus.fi CNAME to smtp.sohva.org) + DKIM signing for verification emails.
-
Auth: Email/password, verified+approved
requirement, JWT session cookie (
session_token), roles.
How the Next.js App Router is wired here
-
Rendering model: Server Components by default for
pages under
app/; Client Components opt in with"use client"(forms, maps, language toggle). This keeps most data fetching server-side and ships minimal JS. -
Routes: File-system routing in
app/; notable paths:app/page.tsx(home),app/browse/page.tsx(search + map),app/listings/[slug]/page.tsx(detail), and admin routes underapp/admin. API handlers live inapp/api/*/route.ts(REST-like endpoints used by forms and fetch calls). -
Data fetching: Server Components call Prisma
directly; where client state is needed, pages expose lightweight
fetchers that hit the API routes. Revalidation uses
fetch(..., { cache: 'no-store' })for sensitive pages (profile/admin) and ISR for mostly-read content (listing details + home feed). -
Mutations: Forms post to API route handlers (e.g.,
auth, listing create/edit, approvals). Responses trigger router
refresh on the client via
useRouter().refresh()so the server-rendered data is re-fetched without a full page reload. -
Auth enforcement:
middleware.tschecks the session cookie early and guards admin/listing edit areas; protected pages also re-validate the session on the server before rendering. The same session util is shared by API handlers to avoid drift. -
Assets & images: Static assets are served from
/publicand cached by Varnish and Next.js. Listing images are streamed through/api/images/:id; the handler sets cache headers while respecting auth where required.