
Problem
The site needed real multi-page behavior, shared content, dynamic detail pages, and believable form flows without pretending to be a production healthcare platform.
Context / users
This is a front-end-heavy practice-site demo. I built it without a framework and used a custom router, modular page structure, and a thin Node/Express server.
My role
I owned the architecture, frontend implementation, routing system, component structure, shared data model, and the Express backend used for demo submissions.
Solution
I split the app into a persistent layout shell, route-aware page modules, reusable sections, and a centralized site data file. Express serves the app and handles contact, referral, and scheduling submissions.
- Custom client-side router with static and dynamic routes for treatment and career detail pages
- History API navigation with internal link interception and route-aware active nav state
- Per-route scroll position save and restore for smoother SPA navigation
- Centralized content model in `siteData.js` for practice info, treatments, careers, scheduling steps, and FAQs
- API-backed demo flows for contact, referral, and scheduling via Express JSON endpoints
- Responsive navigation with mobile menu behavior and body scroll locking
- Client-simulated UI flows for job applications, newsletter signup, and patient portal interactions
Architecture
The client handles layout, routing, and rendering. Express serves static assets, provides a few narrow API endpoints, and falls back to `index.html` for SPA routing. Shared content lives in `siteData.js`, which keeps homepage sections, treatments, careers, FAQs, and form options consistent across the app.
Engineering Details
- • Built as a vanilla JS SPA instead of relying on React or another framework, which forced explicit decisions around routing, lifecycle, and DOM updates
- • Separated persistent layout concerns from route-level rendering so navigation updates the main content area instead of rebuilding the whole page
- • Used a `Map` to preserve scroll positions by route and restored them after navigation for better UX on a multi-view SPA
- • Kept the backend intentionally narrow: static file serving, a site data endpoint, and three demo submission endpoints
- • Used reusable render/init patterns across components to keep DOM binding logic close to the markup it controls
- • Added cleanup for the rotating hero so route changes do not leave timers running in the background
- • Implemented stronger client-side validation and feedback on the referral flow than a simple alert-only form
Outcome
- Produced a practice-site demo that behaves more like an application than a static mockup
- Demonstrated that a small codebase can still support routing, dynamic detail pages, reusable content, and form workflows without a framework dependency
- Created a reusable reference build for future service work where a polished front end matters more than a heavy application stack
- Made demo boundaries explicit by avoiding data persistence and labeling the site as a demonstration experience
Tradeoffs / Limits
- • There is no real authentication, session management, or patient portal backend; those flows are simulated
- • Only contact, referral, and scheduling post to Express endpoints; job applications, newsletter signup, and patient portal flows are intentionally client-simulated interactions
- • Form behavior is not fully consistent across the app; some flows have loading and inline feedback while others still rely on alerts
- • There are no automated tests in the repo
- • The referral page includes healthcare-style confidentiality language, but the demo backend is not implementing production-grade secure data handling
- • Some detail content is extended locally in page modules instead of being fully driven from one normalized data source
Why It Matters
It shows practical SPA architecture without framework shortcuts. It also stays honest about where the demo stops.
Like what you see?
Feel free to reach out if you have questions about this project or want to chat about working together.