5 min read

Building Perpetual Gems: Angular SSR, i18n, and a repair intake API

Perpetual Gems at perpetual-gems.vercel.app: eight-locale ngx-translate, repair workflow UX, and a small Express API for secure form posts — Angular SSR without reaching for Next.

  • Angular
  • TypeScript
  • i18n
  • Express
  • SSR

Building Perpetual Gems: Angular SSR, i18n, and a repair intake API

High-trust local services need translated copy and forms that do not lose leads to CSRF or silent mail failures. Perpetual Gems is a fictional jewelry repair brand I built to prove the same care on Angular 19 that I put into React/Next work.

#TL;DR

Perpetual Gems is a marketing + lead-capture site for a fictional jewelry repair brand — a portfolio piece to prove I can ship Angular 19 with the same care I put into React/Next work. Live at perpetual-gems.vercel.app. Stack:

LayerChoice
FrontendAngular 19, standalone components, Tailwind CSS v4, @ngx-translate
SSRAngular SSR (@angular/ssr) + Express host in client
BackendNode + Express in server/ — repair intake, Nodemailer, CSRF
DeployVercel (vercel.json, postbuild copies browser bundle to public/)
i18n8 locales (US/UK English, ES variants, PT variants, FR, IT) with regional pricing copy

The README still calls the folder franklin-jewelry/ internally — a rename debt from an earlier brand exercise. The engineering is the point: trust-heavy local service UX with real security basics on forms.


#Product goals

Jewelry repair is high-trust, low-frequency. The site optimizes for:

  1. Credibility — gallery, service matrix (rings, chains, stones, restoration), “GIA certified” tone without being cheesy
  2. Conversion — “Start repair request” flow with clear steps and insurance language
  3. Reach — tourists and diaspora customers via locale-aware copy and currency formatting
  4. Operator notification — email when a repair form submits

No database in v1 — email is the system of record. That matches “brochure + intake” scope.


#Frontend architecture

Angular 19’s standalone APIs reduced NgModule ceremony. Layout is componentized (components/, services for translation and API calls). Tailwind v4 via @tailwindcss/postcss keeps styling utility-first without pulling React ecosystem tools.

Internationalization is not an afterthought:

  • Browser language detection on first visit
  • Persistent language cookie (ngx-cookie-service)
  • Per-locale JSON under assets/i18n/
  • Known debt (documented in README): language code mapping duplicated across TranslationService, app.config.ts, and the language switcher — needs a single config export

SSR matters for first paint on marketing pages and for social previews; the Express integration in Angular 19 is smoother than the Universal boilerplate I used years ago, but Vercel still required an explicit postbuild copy into public/ for static assets.


#Backend and security

The server/ package is a focused Express app:

  • CORS locked to known origins in production
  • CSRF middleware on mutating routes — forms must carry a token
  • Environment-driven SMTP (Gmail app password pattern documented)
  • Validation on repair payloads before email send

That is the minimum bar for a public form on the internet in 2025. A pure static site would avoid server cost but would push secrets into serverless functions without structure; the split client/ + server/ monorepo keeps concerns clear.


#Deployment notes

package.json at the root orchestrates:

JSON
"build": "npm run build:server && npm run build:client && npm run postbuild",
"postbuild": "mkdir -p public && cp -r client/dist/client/browser/* public/"

Vercel serves the static browser output; API routes proxy to the Node server per vercel.json. When postbuild broke once in August 2025, the symptom was a blank deploy — fixed by copying client/dist/client/browser/* into public/ after the Angular build.


#What I'd do in v2

ItemWhy
Centralize locale configOne languages.ts consumed by switcher + loader
Repair status portalCustomers expect tracking numbers
Photo upload to object storageAssessment without shipping
Replace fictional metrics“1000+ repairs” undermines trust if literal
E2E smoke on Vercel previewPlaywright against / + form happy path

The Express server sets a readable XSRF-TOKEN cookie on first visit — Angular’s HTTP client mirrors it into the X-XSRF-TOKEN header on mutating requests. CORS uses an explicit origin allowlist with credentials: true; unknown origins fail closed:

TypeScript
app.use(cors({
  origin: (origin, callback) => {
    if (!origin) return callback(null, true);
    if (SERVER.CORS.ORIGIN.includes(origin)) callback(null, true);
    else callback(new Error('Not allowed by CORS'));
  },
  credentials: true,
}));

#Repair intake validation

/repair-requests rejects missing env vars before touching Nodemailer, validates required body fields server-side, and sends two emails — shop notification plus customer confirmation — so a successful HTTP 200 actually means both paths succeeded.


#Closing thought

Repair businesses convert on clarity—what you fix, how long it takes, and a form that does not lose leads to CSRF or silent mail failures. Framework choice matters less than translated copy and a server that validates before it sends email.


#Reader field guide

When this split (Angular SSR + Express API) fits: Marketing site with a mutating lead form; CSRF and CORS matter; email is the system of record in v1; multiple locales with regional pricing copy.

Operational checklist

  • CSRF cookie readable by Angular client; mutating requests carry matching header
  • CORS allowlist explicit in production — unknown origins fail closed
  • Validate repair payload server-side before Nodemailer; return 503 when SMTP env is missing
  • Send shop notification and customer confirmation — 200 means both succeeded
  • Centralize locale codes in one config consumed by switcher + loader (known debt in v1)
  • Postbuild copies browser bundle into public/ for Vercel static + API routing
  • E2E smoke on preview: / load + happy-path form POST

#On this site

PostWhy
Astro + Bun landing template experimentStatic resale shell vs SSR + API when leads must email the shop
Portfolio v1 → v2How this site’s generation compares to token-driven marketing stacks

#References (curated)

Angular’s SSR guide and Express’s security page are the pair I send when someone asks “why two processes on Vercel.”

ReferenceNotes
Live siteProduction deploy—eight locales, repair intake, trust copy.
Angular SSRUniversal + postbuild copy into public/ for static hosting beside the API.
ngx-translateRuntime locale switching—centralize codes (known v1 debt).
Express security best practicesCSRF, CORS allowlists, and rate limits on /repair-requests.