
Problem
Local service sites often need many near-duplicate service/location pages without becoming a pile of hand-maintained HTML.
Context / users
This is a generalized local-service demo adapted from real client work. The useful part is the system: structured location and service data, generated pages, and narrow backend form handling.
My role
I owned the information architecture, content modeling, page generation, component structure, Express routes, form validation, reCAPTCHA integration, email handling, and demo adaptation.
Solution
I kept it static-first. Core pages are plain HTML enhanced by ES-module components. Location pages are generated from structured data. Express only handles the parts that need a server.
- Generated location/service landing pages written from structured data instead of manually copied HTML
- Reusable vanilla-JS component layer for navigation, hero, reviews, contact, careers, service pages, and location pages
- Express-backed contact and quote endpoints with required-field checks, email validation, and reCAPTCHA verification
- Nodemailer notifications and confirmation emails so form submissions behave like a real business workflow
- Careers section with structured job data, job-detail routes, and a general-application path
- SEO-oriented page metadata, schema markup, sitemap generation, and canonical handling
- Static-friendly deployment model that keeps the frontend simple while preserving backend-powered lead capture
Architecture
Presentation, content modeling, generation, and backend form handling are kept separate. `public/components` holds reusable view modules, `public/data` holds source data, `scripts/` handles generation utilities, and `server.js` serves static files and exposes the form endpoints.
Engineering Details
- • Used a static-first architecture to keep most pages cheap to host and fast to serve, while reserving Express for the server concerns that actually require it
- • Modeled location and service content as structured data so the long-tail SEO footprint could be generated instead of hand-maintained
- • Organized the frontend into discrete ES-module components rather than one monolithic script, which makes the site easier to re-skin and extend
- • Added client-side validation in the contact flow and mirrored core validation on the server for a sensible defense-in-depth pattern
- • Handled careers detail pages with client-side route updates and a server catch-all so deep links can still resolve to the correct HTML shell
- • Built demo-mode touches such as booking fallbacks, health/test-email endpoints, and mock testimonial data to make the project usable as a portfolio artifact instead of a dead mockup
Outcome
- Turned a repetitive local-SEO page problem into a reusable generation pattern
- Produced a portfolio-safe demo that shows how a real agency/client implementation can be abstracted into a reusable technical asset
- Created a small full-stack marketing site where the frontend stays lightweight but the lead flows still feel operational
- Established a credible base for future reuse across other service-business builds, especially where static content and lead capture matter more than complex app state
Tradeoffs / Limits
- • The repo is stronger as an architecture demo than as a production-ready product. It has no database, no user accounts, and no fully integrated booking platform
- • The demo is only partially generalized today. Some legacy Heartland-specific branding, metadata, canonicals, and narrative copy still remain in the checked-in code
- • The careers UI is ahead of the backend: file inputs are present, but the checked-in job-application flow does not implement a real resume upload pipeline
- • The page-generation idea is real, but the checked-in generator script still needs module-format cleanup before the build story feels polished from a fresh clone
- • There is no visible automated test suite or CI workflow in the current repo
Why It Matters
It shows how I turn a repetitive SEO problem into a reusable technical pattern.
Like what you see?
Feel free to reach out if you have questions about this project or want to chat about working together.