sakitechnologies

Saki Technologies — Angular SSR website

Dummy-content build cloned from the Evergreen Aqua template. Images in public/images are generated placeholders (node scripts/generate-placeholders.mjs); replace files in place, keeping names. Contact details, social links and domain in src/app/core/config/site.config.ts are placeholders.

Responsive, accessible, SEO-first website for Saki Technologies (Chennai), built with Angular 22 (standalone components, signals, zoneless), Angular SSR + prerendering, and a 5-preset dynamic theme system.


1. Install and run

Requires Node 22.12+.

npm install
npm start                 # dev server with SSR at http://localhost:4200
npm run test:ci           # unit tests once (npm test = watch mode)
npm run build             # production build + prerender → dist/sakitechnologies
npm run serve:ssr         # run the production server at http://localhost:4000
npm run check:seo -- http://localhost:4000   # crawl the sitemap and audit on-page SEO

Dependencies are only Angular, Express and RxJS. No UI kit, icon font or web font is loaded.

Images (self-hosted)

All images live in public/images/ as optimised WebP with descriptive file names (e.g. terrace-softener-installation.webp), plus saki-logo.png and a 1200×630 social-share image saki-og.jpg. The brochure is public/brochures/saki-brochure.pdf. Nothing loads from the old domain.

src/app/core/data/image-manifest.json maps each file name to its original WordPress upload. To regenerate the WebP files (e.g. after changing size or quality), point the script at the backup of the old site’s media:

npm run images -- --from ~/Downloads/saki-media

To add a new image: put the original in that backup (or anywhere), add a "seo-name": "<source url>" line to the manifest, run the script, then use image('seo-name', 'Alt text') in the data files.


2. Project structure

src/
├── index.html                  # pre-paint theme script, favicon
├── server.ts                   # Express: redirects, sitemap, robots, /api/enquiry, caching, SSR
├── server/
│   ├── redirects.ts            # legacy WordPress → new URLs, trailing slash, ?page=1
│   └── enquiry.ts              # validated, rate-limited enquiry endpoint (wire up email here)
├── styles.scss + styles/       # design system: tokens, layout, buttons, cards, forms, tables
└── app/
    ├── app.ts / app.config.ts  # shell (skip link, header, main, footer), hydration, router
    ├── app.routes.ts           # clean URLs, lazy pages, slug guards → real 404
    ├── app.routes.server.ts    # prerender vs SSR per route, 404 status
    ├── core/
    │   ├── config/             # site.config.ts (business + base URL), pages.ts (per-page SEO), navigation.ts
    │   ├── data/               # products, RO purifiers, blog, FAQ, testimonials, gallery, images
    │   ├── seo/                # SeoService, JSON-LD builders, sitemap/robots builders, URL utils
    │   ├── theme/              # 5 presets, ThemeService, WCAG colour helpers
    │   ├── routing/            # slug guards, 404 status, pagination, reuse strategy
    │   └── enquiry/            # form submission service
    ├── shared/
    │   ├── layout/             # header (desktop dropdowns + mobile menu), footer, theme switcher
    │   ├── forms/              # accessible enquiry form (contact, careers, FAQ)
    │   └── ui/                 # button directive, icon, cards, breadcrumbs, page header, state views, CTA
    └── pages/                  # one folder per route
scripts/
├── check-seo.mjs               # post-build SEO audit
└── optimize-images.mjs         # download + convert images
deploy/
├── nginx.conf                  # HTTPS, www→apex, gzip, caching, proxy to Node
└── ecosystem.config.cjs        # PM2 cluster config

Content lives in core/data and core/config — edit those files to change copy, prices, products or contact details. Pages, metadata, JSON-LD and the sitemap all read from the same source, so they can’t drift apart.


3. Themes

Five presets in core/theme/themes.ts: Aqua (brand #3857F1, default), Ocean, Forest, Sunset, Midnight (dark). Users switch from the palette button in the header and can also pick a custom brand colour, which is automatically darkened/lightened until it meets WCAG AA contrast.

How it avoids flashes and layout shift:

  1. Preset CSS variables are rendered into <head> on the server (ThemeService).
  2. A tiny inline script in index.html applies the saved preset/custom colour before first paint.
  3. The choice is saved in localStorage.

To add a preset, append to THEME_PRESETS; theme.spec.ts will fail if any text/background pair drops below 4.5:1 contrast. All component styles use only --color-* tokens.


4. SEO implementation

SeoService (core/seo/seo.service.ts)

One call per page: seo.setPage({ title, description, path, image?, type?, noindex?, breadcrumbs?, jsonLd?, article? })

Structured data (core/seo/schema.ts)

Page Schema
Home LocalBusiness (address, phones, hours, sameAs), WebSite
Contact LocalBusiness
Inner pages BreadcrumbList
Water softener, sand filter Product + AggregateOffer (real MRPs only)
RO purifiers ItemList of Product + Offer
Blog posts BlogPosting
FAQ FAQPage

Deliberately not added: ratings/reviews (self-serving reviews aren’t eligible and none are verifiable), JobPosting (no posting dates/validity published), Product for items without prices. Note: Google currently shows FAQ rich results mainly for government/health sites.

Technical SEO


5. Performance & accessibility


6. Deploy to AWS EC2 (Nginx + PM2)

# on the server (Ubuntu), first time
sudo apt install -y nginx && sudo npm i -g pm2
sudo mkdir -p /var/www/sakitechnologies && sudo chown $USER /var/www/sakitechnologies

# each release (build locally or in CI, then copy)
npm ci && npm run test:ci && npm run build
rsync -az --delete dist package.json package-lock.json deploy user@server:/var/www/sakitechnologies/
ssh user@server 'cd /var/www/sakitechnologies && npm ci --omit=dev && pm2 startOrReload deploy/ecosystem.config.cjs && pm2 save'

# Nginx (first time)
sudo cp deploy/nginx.conf /etc/nginx/sites-available/sakitechnologies.com
sudo ln -s /etc/nginx/sites-available/sakitechnologies.com /etc/nginx/sites-enabled/
sudo certbot --nginx -d sakitechnologies.com -d www.sakitechnologies.com
sudo nginx -t && sudo systemctl reload nginx
pm2 startup   # restart on reboot

Environment variables: PORT (4000), SITE_URL (sitemap/robots base), SITE_INDEXABLE (false on staging). If you deploy to a different domain, also update SITE.baseUrl in site.config.ts (canonicals and JSON-LD) and security.allowedHosts in angular.json.

Before going live: wire up deliver() in src/server/enquiry.ts (email/CRM) — enquiries are only logged right now. Swap the text logo in header.html for the real logo if you prefer.


7. SEO validation checklist

Before launch (local)

Google Rich Results Test (https://search.google.com/test/rich-results)

Lighthouse (Chrome DevTools → Lighthouse, Mobile, on the production URL)

Google Search Console (after deploy)

Ongoing