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:
| Layer | Choice |
|---|---|
| Frontend | Angular 19, standalone components, Tailwind CSS v4, @ngx-translate |
| SSR | Angular SSR (@angular/ssr) + Express host in client |
| Backend | Node + Express in server/ — repair intake, Nodemailer, CSRF |
| Deploy | Vercel (vercel.json, postbuild copies browser bundle to public/) |
| i18n | 8 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:
- Credibility — gallery, service matrix (rings, chains, stones, restoration), “GIA certified” tone without being cheesy
- Conversion — “Start repair request” flow with clear steps and insurance language
- Reach — tourists and diaspora customers via locale-aware copy and currency formatting
- 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:
"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
| Item | Why |
|---|---|
| Centralize locale config | One languages.ts consumed by switcher + loader |
| Repair status portal | Customers expect tracking numbers |
| Photo upload to object storage | Assessment without shipping |
| Replace fictional metrics | “1000+ repairs” undermines trust if literal |
| E2E smoke on Vercel preview | Playwright against / + form happy path |
#Angular 19 CSRF cookie pattern
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:
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
#Related reading
#On this site
| Post | Why |
|---|---|
| Astro + Bun landing template experiment | Static resale shell vs SSR + API when leads must email the shop |
| Portfolio v1 → v2 | How 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.”
| Reference | Notes |
|---|---|
| Live site | Production deploy—eight locales, repair intake, trust copy. |
| Angular SSR | Universal + postbuild copy into public/ for static hosting beside the API. |
| ngx-translate | Runtime locale switching—centralize codes (known v1 debt). |
| Express security best practices | CSRF, CORS allowlists, and rate limits on /repair-requests. |