# Vinay Bandi — Lead Frontend Engineer > Lead Frontend Engineer with 11+ years of experience building and scaling frontend platforms for high-traffic, SEO-critical products in healthcare and multi-brand environments. This is the full text content of the personal portfolio at https://www.vinaybandi.com. Contact: vinay.26dev@gmail.com · Based in Chicago, IL. Currently building at The Aspen Group (TAG). This file inlines the readable content of every page on the site. For the link-only index, see /llms.txt. --- # Home — https://www.vinaybandi.com Lead Frontend Engineer. I engineer frontend platforms that scale across teams, brands, and tens of thousands of pages. 11+ years building and scaling frontend platforms for high-traffic, SEO-critical products — serving 1,000+ locations across dental, emergency, veterinary, and consumer health with national SEO traffic. Capabilities: Platform ownership at scale · Multi-brand · multi-domain · SEO-critical platforms · Design systems & CMS. ## By the numbers - 11+ years building platforms - 1,000+ locations served - 10k+ pages at scale - 5+ brands unified ## Platform & Tooling — the stack behind production platforms Tools used to build, test, ship, and operate frontend platforms in production — from the design system and content layer to CI/CD, observability, and Core Web Vitals. - Frontend Platform (core architecture and shared app state): React, Next.js (App Router), TypeScript, Zustand, React Hook Form, Zod - Design System & UI (component foundations and living documentation): MUI, Tailwind CSS, shadcn/ui, Storybook - Content & SEO (content modeling, delivery, and SEO reliability): Contentful, GraphQL (Content APIs), Contentful Studio, ContentKing, Technical SEO - Testing & Quality (automated confidence and code-quality gates): Vitest, React Testing Library, Playwright, ESLint, Prettier, Conventional Commits, SonarQube - Delivery & Infra (CI/CD, infrastructure, and safe rollouts): GitLab CI, Argo CD, Spinnaker, Kubernetes, CloudFront, LaunchDarkly - Observability & Performance (production health and Core Web Vitals): Sentry, GCP Monitoring, Core Web Vitals, Lighthouse, CrUX, Search Console ## How I work — principles that guide the build - Systems that scale with the business: design frontend platforms that absorb new brands, pages, and teams without rewrites. - Decisions with clear tradeoffs: make architectural calls explicit — weighing performance, cost, and delivery speed. - Performance, SEO & accessibility: treat Core Web Vitals, crawlability, and a11y as first-class production constraints. - Less engineering toil: empower content teams to ship safely, keeping engineering off the critical path. --- # About — https://www.vinaybandi.com/about Engineering frontend platforms that teams can build on. I'm a Lead Frontend Engineer with 11+ years of experience building and scaling frontend platforms for production systems with real constraints: performance, SEO reliability, content velocity, and multi-brand growth. I focus on platform-level architecture — design systems, content systems, and delivery pipelines that reduce engineering toil and enable teams to move faster without sacrificing quality. My approach is simple: clear ownership, strong standards across performance, accessibility, and SEO, and systems that scale with the organization rather than fighting it. I've led migrations of large healthcare platforms, unified multiple brands onto a single codebase and design system, and reshaped CMS architecture so content teams can ship without waiting on engineering. Focus areas: Frontend platform architecture · Design systems & theming · Content systems (Contentful) · Technical SEO at scale · Performance & Core Web Vitals · CI/CD & observability. --- # Stats — https://www.vinaybandi.com/stats How this site is built & shipped. A colophon for the curious — the stack this portfolio runs on, where it's deployed, and the build behind the version you're reading right now. Every value here is derived at build time, not hand-written. Built with: Next.js (App Router), React, TypeScript, Tailwind CSS, shadcn/ui (new-york style), lucide-react, next-themes, and web-vitals. Tooling & conventions: Radix UI, class-variance-authority, pnpm, ESLint, Prettier, Vitest, and Vercel Speed Insights. Typography: Geist (headings & body) and Geist Mono (code, labels & metadata), self-hosted through next/font with no third-party font CDN request. Infrastructure: Hosted on Vercel, deployed from GitHub via CI. Pages are statically prerendered (SSG) and served from Vercel's edge CDN. Package manager: pnpm. Domain: www.vinaybandi.com. Build metadata: the page surfaces the current commit, branch, last deploy date, static route count, and Node version — all read from the environment and git at build time. Performance: because every route is prerendered and edge-served, pages arrive as ready-made HTML. Real Core Web Vitals for the current session are measured live in the footer of every page, and can be independently verified on PageSpeed Insights. For machines: the site publishes machine-readable endpoints — sitemap.xml (routes for crawlers), robots.txt (crawl rules), llms.txt (a concise link index), and llms-full.txt (every page's content inlined) — for search crawlers and language models. --- # Work — https://www.vinaybandi.com/work Case studies in scale & reliability. Selected case studies focused on platform scale, multi-brand architecture, and content systems — the work behind high-traffic, SEO-critical products. ## Scaling an SEO-Critical Web Platform at Aspen Dental — https://www.vinaybandi.com/work/seo-platform Status: Completed. Stack: Next.js, Contentful, CloudFront, SSR, Technical SEO. Aspen Dental runs a large, SEO-critical site of tens of thousands of pages. I migrated it from Gatsby SSG to Next.js SSR and the App Router — decoupling content publishing from deployments, improving release velocity, and stabilizing performance and SEO at scale. Context: Aspen Dental is a U.S. healthcare organization with 1,000+ offices nationwide. The public website includes tens of thousands of pages and receives heavy traffic from search engines and crawlers, making SEO reliability and performance business-critical. When I joined in 2022, the platform was built with Gatsby + Contentful using static site generation (SSG). Problem: - Content updates required manual production deployments via Spinnaker - Editors depended on engineers to publish changes - Static builds became fragile as page count increased - Release velocity slowed for SEO-critical updates - Performance and crawl reliability were tied to build success My Role: - Led frontend platform migration strategy - Drove decisions with SEO, scale, and performance as core constraints - Led cross-functional collaboration with content, SEO, platform, and DevOps teams - Owned rollout quality, caching strategy, and operational reliability Approach & Key Decisions: - Next.js Pages Router + SSR: decoupled content publishing from CI/CD; enabled near real-time publishing from Contentful; eliminated manual production deploys for content updates; improved release velocity while preserving SEO stability. - CloudFront caching strategy: cached static assets aggressively with long TTLs; used shorter, controlled caching for HTML/content responses; prevented stale content while improving repeat-visit performance. - App Router migration: adopted React Server Components where appropriate; reduced client-side JavaScript on key routes; improved caching control and performance consistency. - SEO monitoring & alerts: integrated SEO audit tooling (e.g., ContentKing); alerted on broken pages/links and routed to content teams; reduced time-to-fix and engineering involvement in routine SEO cleanup. Results: near real-time content publishing; eliminated manual deploys for content updates; improved release velocity for SEO-critical pages; better performance and caching consistency at scale; reduced operational risk for a national healthcare platform. ## Building a Multi-Brand Frontend Platform for The Aspen Group — https://www.vinaybandi.com/work/multi-brand-platform Status: Completed. Stack: Multi-brand, Config-driven, Design system, Contentful, Theming. The Aspen Group runs several brands, not one. Working with our principal engineers, I helped architect a shared frontend platform that serves all of them from a single codebase and design system — onboarding new brands faster and reducing long-term maintenance and engineering effort. Context: Aspen Dental transitioned to The Aspen Group and expanded into a multi-brand organization (Aspen Dental, ClearChoice, WellNow, Lovet, Chapter, and more). Problem: - A single-brand architecture doesn't scale as new brands are added - Separate apps per brand would multiply complexity and maintenance cost - Content teams needed flexibility without engineering dependency My Role: - Contributed to the platform strategy and implementation - Helped ensure the architecture stayed simple and maintainable - Worked with content/platform teams on brand configuration workflow Approach & Key Decisions: - Two apps, clear responsibilities: a Digital marketing app (all landing pages and marketing content) and an Office directory app (all office/location pages); one codebase supports multiple brands without forks. - Contentful-driven brand configuration: editors set up content per brand per app in Contentful; apps query Contentful using brand + app context. - Shared design system with theming: single component system shared across brands; brand-specific theme/config applied to the same components; consistent behavior and accessibility across brands. Results: one marketing app and one office directory app support multiple brands; new brands can be onboarded faster using existing foundations; reduced duplication and long-term maintenance cost; content teams can manage brand content with minimal engineering effort. ## CMS 2.0 — Decoupling Content and Design at Scale — https://www.vinaybandi.com/work/cms-2 Status: Completed. Stack: Contentful Studio, CMS architecture, Composition, Design tokens. Content types were multiplying, and every design change waited on engineering. I redesigned the CMS architecture to separate content from presentation — cutting content-type sprawl and letting content teams build new layouts and designs without engineering involvement. Context: As The Aspen Group added more brands and more content requirements, the number of Contentful content types grew rapidly and became harder to maintain. Problem: The existing model mapped one Contentful content type to one React component. Design changes required content model changes + code changes + sprint and deployment cycles, slowing marketing iteration and keeping engineering on the critical path. It also meant hundreds of entries per type, so even a simple field change had to be applied to every entry by hand. In the near term I addressed that with a library of batch-update scripts (/engineering/contentful-batch-updates); CMS 2.0 was the structural fix. My Role: - Heavy contributor to CMS 2.0 exploration and implementation - Helped shape the architecture to reduce content type sprawl - Partnered with design/content teams to define a new workflow Approach & Key Decisions: We explored Contentful Studio to separate content from presentation: fewer core content types, with flexible composition for layouts. Composition with core atoms — define a small set of reusable "atoms" / building blocks; editors assemble layouts using atoms and bind content; new designs can be created without new content types or new components. That small set of atoms became the backbone of every page — which later made keeping the library clean its own challenge. I wrote a custom audit (/engineering/contentful-atoms-audit) to track which experiences use which atoms, so they can be versioned and retired safely. Results: reduced pressure to create new content types for every new design; faster design iteration for marketing/content teams; clear path to removing engineering from routine layout changes. Why This Matters: CMS 2.0 shifts engineering from being a delivery bottleneck to being a platform enabler — improving time-to-market and reducing long-term complexity. ## Server-Side Personalization at Scale — https://www.vinaybandi.com/work/server-side-personalization Status: Completed. Stack: Next.js, SSR, CloudFront, Cookies, Contentful, Technical SEO. Aspen Dental's pages needed to speak to the visitor's nearest office — the right name, phone, and city — without slowing the site down or breaking SEO. I built a personalization pipeline that resolves the visitor's office on the server, replaces content tokens before the HTML is sent, and renders a fully personalized first paint with no client-side flicker or layout shift. Context: Aspen Dental is a U.S. healthcare organization with 1,000+ offices nationwide, and its public site is a large, SEO-critical platform of tens of thousands of pages. Marketing wanted landing pages that reflected the visitor's closest office — surfacing the local name, phone number, city, and nearby landmarks — instead of generic, one-size-fits-all copy. This built directly on the Gatsby to Next.js SSR migration (/work/seo-platform), which already put server-side rendering and a CloudFront edge in front of every page. Problem: The obvious way to personalize — read the location in the browser and swap the text after the page loads — is exactly the wrong approach for a site like this. Client-side replacement flickers: the generic content paints first, then jumps to the personalized version, causing visible layout shift (poor CLS). Search crawlers and social scrapers see the generic, pre-JavaScript markup, so the personalized content is unreliable for SEO. And location logic scattered across components is hard to keep consistent. Personalization had to be resolved and rendered before the HTML leaves the server, so the first byte a visitor (or a crawler) receives is already correct. My Role: designed the server-side office-resolution and token-replacement pipeline; defined the precedence rules that make personalization deterministic; defined the set of personalization tokens available to authors; owned the SEO and performance constraints — no flicker, no layout shift. Resolving the "home office": every request resolves a single home office using a strict order of precedence. Explicit choice first — a cookie, set when a visitor picks an office, takes highest priority because it represents deliberate intent. Then geolocation — with no cookie, the CDN provides latitude/longitude headers at the edge (via CloudFront), which we use to find the nearest office to those coordinates. Then a safe fallback — if neither resolves, personalization falls back to a neutral default rather than failing. Server-side token replacement: on the initial load the server resolves the office, fetches the matching Contentful experience, replaces the content tokens, and renders the final HTML in one pass. Authors write placeholders directly into their copy — officeName, officePhone, officeCity, officeState, and officeLandmarks — and the server fills them in before the page is sent. Returning visitors and explicit choices: when a visitor changes their office, a Server Action updates the cookie and the UI reruns the replacement logic to refresh the content in place — no full page reload. The cookie is persistent (max-age of one year), so return visits reflect the visitor's last choice straight from the server. Why it's server-side: because the office is resolved and the tokens are replaced before render, the first paint is already personalized. There is no generic-then-personalized swap, so there's no flicker and no layout shift — and crawlers receive complete, stable HTML, keeping the personalization SEO-safe. The composition side of this — how editors build location-specific layouts on top of the resolved office — is covered in Composable Personalization with Contentful Studio (/work/composable-personalization). Results: - Personalized first paint — the nearest office's details are in the HTML before it's sent. - No flicker and no layout shift, because nothing is swapped after load. - SEO-safe: crawlers see complete, stable, personalized markup. - Deterministic resolution via cookie then geolocation then fallback precedence. - Return visits honor the visitor's last chosen office through a persistent cookie. Why This Matters: Personalization usually trades performance and SEO for relevance. Resolving it on the server flips that trade-off: visitors get locally relevant pages, crawlers get stable markup, and Core Web Vitals stay intact — all from one pass on the server. ## Composable Personalization with Contentful Studio — https://www.vinaybandi.com/work/composable-personalization Status: Completed. Stack: Contentful Studio, Custom atoms, Composition, SSR, Personalization. Replacing office name and phone with a token is one kind of personalization. Showing a different layout entirely — a video for one location, an image background for another — is harder, and it shouldn't require engineering every time. I built the Segments Container, a Contentful Studio atom that lets marketing author location-specific variants by composition, with the server rendering only the segment that matches the visitor's office. Context: Once pages could resolve a visitor's nearest office on the server, the next request was richer: marketing wanted different content for different locations — not just swapped words, but entirely different sections. A flagship office might lead with a video; another might use a half-screen image; everywhere else should see a sensible default. This is the authoring layer on top of Server-Side Personalization (/work/server-side-personalization), which resolves the facility code this system renders against. Problem: Without a composition primitive for this, every location variant becomes a burden. Each variation means a separate page to build and maintain, or a code change per layout. Editors can't target content by location themselves — they have to file engineering work. And toggling variants in the browser reintroduces the flicker and layout shift that server-side rendering was meant to eliminate. The goal was to let editors build location-specific layouts with the same atoms they already use, and have the right one chosen on the server. My Role: designed and built the Segments Container and Segment Item atoms in Contentful Studio; defined the facility-code matching and fallback rules; implemented server-side selection so only the matching segment renders; shaped the authoring model and guidance so it stays predictable for editors. A "smart folder" for segments: the Segments Container behaves like a smart folder. Editors drop it onto a page, then add a Segment Item for each variation. Each Segment Item has a Facility IDs field and can contain any atoms — images, text, buttons, whole sections — just like anywhere else in Studio. It's composition, not configuration. The match logic: at render time the container looks at the visitor's resolved facility code and shows the single best match. A Segment Item listing 0001, 0002 is shown to visitors near those offices; another listing 0003, 0004 targets a different set of locations. A Segment Item with an empty Facility IDs field is the default — everyone who doesn't match a targeted segment sees it. Facility IDs are matched exactly, so codes are entered without extra spaces; IDs are kept unique per segment, and if more than one somehow matches, the first one wins. Always keep a default: the default (empty) segment is the safety net. If a visitor's office doesn't match any targeted segment and there's no default, the container simply renders nothing — leaving a hole in the page. The guidance is therefore to always include one empty segment as a catch-all, and to put the shared content there so only the genuinely special locations need their own segment. Editor view vs. live site: in Studio, editors see all segments stacked so they can edit every location's version side by side. On the live site the server hides everything except the one segment that matches the visitor. Rendered on the server: selection happens server-side using the facility code from the personalization pipeline, so the matching layout is in the HTML from the first paint. Different offices can get fundamentally different renderings — video, image background, half-screen — with no client-side flicker and no layout shift. Results: - One page serves many locations — no separate page or code change per variant. - Editors compose location-specific layouts with the atoms they already know. - A required default segment guarantees every visitor sees something sensible. - The matching segment renders on the server, so there's no flicker or layout shift. - Engineering is out of the loop for routine location targeting. Why This Matters: Personalization scales only when the people closest to the content can author it. By making location targeting a composition primitive in Studio — and resolving the match on the server — marketing can ship meaningfully different experiences per location without new pages, new components, or a regression in performance and SEO. ## Digitalization of Forms — From Static Documents to Dynamic Experiences — https://www.vinaybandi.com/work/digital-forms Status: In Progress. Stack: React Hook Form, Zod, Contentful, Schema-driven, Accessibility. Too many forms still lived as static documents and one-off builds. Alongside our Principal Engineer, I’m replacing them with a dynamic, schema-driven forms platform — validated, accessible, and reusable across brands. Context: Across the platform, many forms still lived as static documents or one-off, hand-built implementations. Each new form meant repetitive engineering work, and changes were slow to ship. Problem: Static and bespoke forms share the same costs: no shared validation, inconsistent accessibility, duplicated UI, and tight coupling to engineering for every field change. Content and marketing teams couldn't iterate, and quality varied form to form. My Role: - One of the two primary engineers driving the initiative, alongside our Principal Engineer - Helping shape the architecture for schema-driven, reusable forms - Leading frontend implementation and the shared field component library Approach & Key Decisions: Treat a form as data, not as a hand-coded layout: a single schema describes fields, validation, and behavior, and the platform renders and validates from that source of truth. Schema-driven foundations — Zod schemas as the single source of truth for shape and validation; React Hook Form for form state, performance, and validation wiring; Contentful to configure and compose forms without code changes; a reusable, accessible field component library shared across brands. Goals: consistent validation and accessibility by default; new forms created and changed without bespoke engineering; reusable across brands from a single platform; less engineering toil and faster iteration for content teams. Why This Matters: Forms are critical data-capture and conversion surfaces. A platform approach makes them reliable and accessible by default, while freeing engineering from routine form work. --- # Experience — https://www.vinaybandi.com/experience A journey across states. Six roles, six cities, two countries, 11+ years — from jQuery beginnings in Delhi to leading React platform architecture in Chicago. ## The Aspen Group (TAG) — Lead Software Engineer (2022 — Now, Chicago, IL) — current Lead the architecture of modern, scalable web applications on Next.js (App + Pages Router) with GraphQL and REST, across a multi-brand healthcare platform serving 1,000+ locations. - Migrated an SEO-critical platform (tens of thousands of pages) from Gatsby SSG to Next.js SSR + App Router, decoupling publishing from deployments. - Architected a shared multi-brand frontend powering Aspen Dental, ClearChoice, WellNow, Lovet, and Chapter from one codebase and themed design system. - Built reusable React component libraries and schemas; drove test automation with Jest, Vitest, and Cypress in GitLab CI/CD, with Sentry runtime monitoring. - Mentor engineers, run code reviews, and set team-wide standards. Stack: Next.js, React, TypeScript, GraphQL, Material UI, Tailwind, Cypress, Sentry. ## Cardinal Intellectual Property — Sr → Lead Front End / ReactJS Developer (2019 — 2022, Evanston, IL) Grew from Senior to Lead, owning React product work for the company’s IP and marketing tools. - Built interactive React applications with Redux state management and Formik-driven forms. - Developed reusable component libraries shared across products. - Established a strong Jest unit-testing culture on the team. Stack: React, Redux, JavaScript, Material-UI, Kendo React, Formik, Jest. ## Staples — UI Developer / ReactJS Developer (2018 — 2019, Framingham, MA) Built React single-page apps within an Agile product team. - Developed responsive, accessible SPAs with reusable UI patterns. - Managed application state with Redux and implemented drag-and-drop interactions. - Shipped through Jenkins CI/CD pipelines to test environments. Stack: React, Redux, JavaScript, SASS, Node.js, Jenkins. ## Adobe — Front End / ReactJS Developer (2016 — 2018, San Jose, CA) Built interactive, responsive UI experiences on React-based products. - Implemented responsive interfaces with React and Bootstrap across screen sizes. - Created reusable front-end libraries shared across products. - Added JEST test coverage to core UI components. Stack: React, JavaScript, SASS, Node.js, Bootstrap, Git. ## BBVA Compass — UI Developer (2015 — 2016, Birmingham, AL) Delivered banking single-page applications with AngularJS. - Wrote custom AngularJS directives and filters for reusable behavior. - Built responsive UIs with Bootstrap. - Unit-tested components with Karma and Jasmine. Stack: AngularJS, JavaScript, Bootstrap, SQL, Karma, Jasmine. ## GlobalLogic — UI Developer (2014, Delhi, India) Began my career as a UI developer, building web interfaces end to end. - Built interactive, user-friendly interfaces with jQuery and Ajax. - Integrated SOAP/XML web services in a JSP/MVC stack. - Contributed to end-to-end testing of delivered features. Stack: JavaScript, jQuery, Ajax, XML, JSP, MVC. --- # Engineering — https://www.vinaybandi.com/engineering Problems beyond the feature backlog. The work that keeps a platform healthy rarely ships as a feature: custom audits, batch automation, and the guardrails that stop a codebase and CMS from drifting. These are notes on that kind of engineering — each one running in production today. ## Observability for an SEO-Critical Frontend Platform — https://www.vinaybandi.com/engineering/frontend-observability Category: Observability. Stack: Sentry, GCP (Pino structured logging), Core Web Vitals, Next.js use cache, Brandfolder, Contentful. Status: In production. A server-rendered, SEO-critical platform fails in ways a green build can't show: a third-party script regresses Core Web Vitals, an upstream API slows the server render, a cache quietly stops hitting. I instrumented the platform end to end in Sentry and Google Cloud — real-user vitals, server-side dependencies, cache health, structured logs correlated with traces, and third-party noise — so regressions surface in a dashboard before they surface in rankings. The stakes: On a platform where organic search drives the traffic, the expensive failures are the quiet ones — a layout shift from a late script, a Brandfolder call that got slow, a personalization token that stopped resolving. None throw an error or fail the build, but all cost rankings or conversions. Every page is rendered on the server per request, pulling from Contentful, Brandfolder, and the facilities directory — which keeps pages fast, crawlable, and personalized, but also means the render depends on systems I don't fully control. Observability is how I keep it honest. Architecture at a glance: a visitor request (with UTM params) hits the Next.js server render, which loads a Contentful experience, calls Brandfolder REST for asset metadata, reads a cached facilities lookup (internal infrastructure, ~99.8% hit rate), and replaces personalization tokens before the HTML is delivered; in the browser, real-user Core Web Vitals and third-party scripts (GTM, VWO, Freshpaint) run. All of it is instrumented into two correlated sinks — Sentry (traces, spans, Core Web Vitals, errors) and structured Pino logs emitted during the render and shipped to Google Cloud Logging — correlated by request id and tagged with each release. Core Web Vitals at p75: Real-user vitals stream into Sentry and are dashboarded at the 75th percentile — the percentile Google grades — because an average hides the slow tail that actually gets scored. Over the last 24 hours, production p75 reads LCP 2.10s, INP 185.59ms, CLS 0.093, FCP 1.33s, and TTFB 668.02ms — all in Google's "good" band — and every event, trace, and log is tagged with the release that served it, so a regression ties straight back to the deployment that introduced it — it's obvious which release caused it, not a guess. Sampling is deliberate: at production traffic a fraction of sessions is sampled, enough to keep the percentiles stable while keeping event volume, cost, and noise down. The external dependencies that gate a render: SSR is only as fast as the systems it calls. Three are timed as their own spans — Brandfolder metadata (server-side REST calls that resolve asset metadata during render), Contentful experience load time (the composed Studio experience must load before the page renders), and Brandfolder CDN asset delivery (real-user load performance for the images themselves). Splitting them out means a slow page names the dependency that moved instead of a single opaque "server was slow." Caching the facilities lookup: Every request needs the facilities collection — the office directory behind routing, personalization, and office pages. It's wrapped in Next.js use cache, and each lookup is traced with a cache.hit attribute. In production that's roughly 134K cache hits against 320 misses in a 24-hour window — about a 99.8% hit rate — with cached reads near 6.9ms at p90 and even misses near 8.5ms at p90. A drop in that ratio surfaces immediately and usually means a cache key or revalidation regressed. Third-party scripts, correctness, and attribution: Tag managers and experimentation tools (GTM, VWO, Freshpaint) run in the browser and throw errors I don't own; I fingerprint errors originating from third-party script frames so they're counted but never share the first-party error budget or page anyone. Server-side personalization (/work/server-side-personalization) replaces tokens like office name and phone at render time, but only on the experiences where marketing has added them — many pages carry none. Where tokens are used, I track the variable-replacement step to confirm they resolve where expected and catch silent failures. And marketing runs media campaigns across channels — paid social (Meta), video (YouTube), and local listings (Google Places) — that land with UTM parameters, so I track those requests to make campaign traffic measurable. Structured logging, correlated with traces: Dashboards say something regressed; logs say why. The platform emits structured JSON logs through Pino, shipped to Google Cloud Logging, and every line carries the request's context and shares identifiers with the Sentry trace for that same request. When a trace looks slow or throws, I pivot straight to the correlated logs in GCP for that exact request — context already attached — instead of grepping free text; and because the logs are structured, I can filter and aggregate by route, status, or facility. Correlated request context alongside Sentry traces turns "something broke in production" into a specific, investigable request. Production impact: - Core Web Vitals tracked at p75 against Google's thresholds — currently all "good." - Every event, trace, and log is tagged with its release, so a regression ties back to the exact deployment that introduced it. - Structured JSON logs (Pino to Google Cloud) carry correlated request context, so a Sentry trace links straight to the logs for that exact request. - Server-side dependencies (Brandfolder metadata, Contentful experiences) timed individually, so a slow render points to a specific upstream. - Facilities lookup cached with Next.js use cache at a ~99.8% hit rate, verified in production rather than assumed. - Third-party script errors (GTM, VWO, Freshpaint) isolated from the first-party error budget. - Personalization token replacement monitored, so a silent resolution failure is caught, not shipped. - Campaign traffic attributable via UTM tracking, and Brandfolder CDN asset delivery measured on real sessions. ## Batch-Editing Contentful Entries at Scale — https://www.vinaybandi.com/engineering/contentful-batch-updates Category: Automation. Stack: Contentful, Management API, Node, Batch tooling. Status: In production. In CMS 1.0, one content type maps to hundreds of entries — so a single copy, SEO, or field change can mean editing every one by hand. I built a library of targeted scripts that apply the change across every matching entry — validated in Testing, then run in production. The problem: The CMS 1.0 model maps one content type to many entries — often hundreds. A page per office, per location, per brand. That's manageable until a change needs to land on all of them at once: a new meta-title pattern, a canonical domain fix, a hero tweak, a background color. Doing that in the Contentful UI means an editor opening every entry, making the same edit, saving, and publishing — hundreds of times. It's slow, mind-numbing, and exactly the kind of repetition where mistakes slip in unnoticed. The structural fix for this sprawl was CMS 2.0 (/work/cms-2), but the thousands of 1.0 entries that already exist still need bulk maintenance today. What editors ask for (variations of "make this one change everywhere it applies"): - Rewrite SEO title / description patterns across a brand's office pages. - Repoint or fix canonical URLs (e.g. swap a production domain for staging). - Update hero fields, background colors, accordions, and multi-card content. - Swap image assets or refresh blog detail pages. - Bulk-create or delete entries when a content model shifts. Approach — every script follows the same shape, built on the Contentful Management API: - Connect to a space and environment via the CMA client. - Fetch a precise slice of entries with getEntries — a content type plus query filters that select exactly the entries that need changing. - Mutate the localized field(s), then update() and publish() each entry. - Process in small batches with a short pause between them to stay under CMA rate limits. Everything is parameterized by environment, so the identical script can run against Testing or master just by changing one value. How a representative update script works (updatePageCanonical.js): using credentials from environment variables, it connects to the Contentful Management API and points at the Testing environment first, switching to master only after the run has been validated. It fetches only the published "page" entries whose canonical field matches "www.clearchoice.com" — never the whole content type. Those entries are processed in small batches of three; for each one it rewrites the canonical URL (swapping "www" for "wwwstg"), saves the change, and republishes the entry, logging every id it touches. Between batches it pauses a few seconds to stay under Contentful's rate limits, and the whole run stops on the first error. Targeting the right entries: the safety of a batch job lives in the query. Rather than fetch a whole content type and filter in code, each script uses Contentful's search operators ([in], [ne], [match], and sys.publishedAt[exists]) to pull back only the entries that should change — so a run can't accidentally touch anything outside its intended scope. Testing first, always: no batch job goes straight to production. Point ENVIRONMENT_ID at Testing, run the script, and spot-check the results; once validated, run the identical script against master. Small batches plus per-entry logging make it obvious what changed — and easy to stop early if something looks off. How it works day to day: editors never touch the scripts. They describe the change in plain language — "update the meta title on every Aspen Dental office page" — and we translate it into a targeted script. We run it in Testing, share the result for sign-off, then run the same script in production. A change that would take an editor a full day of clicking lands in a few minutes. Production impact: - Bulk edits land in minutes instead of days of manual clicking. - Every matching entry gets the exact same change — no drift, no missed pages. - Editors are unblocked: they request, we run. - A Testing dry-run makes every production change low-risk and reviewable. - A growing, reusable library covering the content types that change most. ## Auditing Contentful Studio Atom Usage — https://www.vinaybandi.com/engineering/contentful-atoms-audit Category: Audit. Stack: Contentful Studio, Delivery API, Node / TypeScript, ExcelJS. Status: In production. Studio experiences are assembled from a shared library of atoms — our custom components. When we ship a new version of an atom, we can't remove the old one until every experience using it has moved over. This tool reports exactly which experiences use which atoms, and how often, so that migration is a checklist instead of a guess. The problem: In Contentful Studio, authors build pages by composing atoms — our registered custom components — into an experience. That decoupling is the whole point: authors ship layout without engineering. But it also means the layout lives in Contentful, not in git. That creates a specific, recurring pain around atom versioning. When we build a v2 of an atom, we can't safely delete v1 until every experience still referencing it has been migrated — and there's no way to see that from the codebase. Grepping the repo tells you nothing, because the usage lives in the CMS. There's a second wrinkle: patterns — reusable compositions of atoms that authors can drop into a page. An atom used inside a pattern is real usage, so a naive scan of the top-level tree under-counts. What the report answers, on demand: - Which experiences use atom X, and how many times? — the exact migration list before you change or retire it. - How is each atom used across the whole space? — a ranking of what's load-bearing versus barely touched. - What's on a given experience? — a per-experience breakdown, atom by atom. - Has the catalog drifted? — any definitionId not in the registry surfaces as category Unknown, a signal the audit's component list has fallen behind the app. Approach: Each experience stores its layout as a nested tree, where every node references a component by its definitionId. So the audit is a tree walk with a couple of Contentful-specific details: - Pull every experience entry through the Delivery API, paginating (limit / skip) with include: 2 so linked pattern entries resolve. - Walk each experience's componentTree, counting every definitionId occurrence. - Expand patterns: when a node matches a usedComponents entry, recurse into that pattern's own tree — guarded so a pattern is only expanded once. - Map each definitionId to a friendly name and category via a registry kept in sync with the app's registered components. Delivery API, not Management: the report only needs to read resolved experience structure, so the Delivery API is the right tool — and a --preview flag swaps in the Preview API to include drafts. An --env flag targets master or Testing, so the same script audits any environment. Fetching and paginating every experience (src/contentful-client.ts): the client reads published content through the Delivery API — or the Preview API when the --preview flag is set — using the space, environment (master or Testing), access token, and host from configuration. It requests experiences in pages of 20, and for each page sets an include depth of 2 so linked pattern entries (usedComponents) are resolved. It keeps requesting pages until it has collected every experience, then returns the full list. The traversal is the interesting part (src/tree-traversal.ts): a recursive function walks each experience's component tree and tallies how many times every definitionId appears. Whenever a node turns out to be a pattern — its definitionId matches one of the experience's usedComponents — the function descends into that pattern's own tree and counts the atoms nested inside it too. A visitedPatterns set guards against expanding the same pattern more than once (and against cycles), so a shared pattern is only expanded once per experience. It returns a map of each definitionId to its usage count. The output: the script prints a summary table sorted by how many experiences use each atom, then writes a styled Excel workbook via ExcelJS with two sheets: a Summary (atom → experiences using it + total usage) and a Detail sheet (one row per experience/atom pair). Excel because the people deciding what to deprecate aren't always engineers. Running it: - pnpm report for prod (master), pnpm report:stage for Testing, and --preview to include drafts. - Two manual GitLab CI jobs — one per environment — that produce the Excel workbook as a downloadable artifact. - A small component registry that's kept in sync as new atoms are registered in the marketing app. In the field: A while after this shipped, marketing wanted to retire the Google Maps atom — but first they needed to know which experiences still used it. Contentful's UI gives you no way to answer that question. When they reached out, I reminded them about this script. I triggered the GitLab job, downloaded the Excel, and sent back the exact list of experiences — in about two minutes. What would have been a risky guess became a quick, confident cleanup. Production impact: - Atom versioning is safe: a concrete list of experiences to migrate before removing anything. - Pattern-aware counts, so usage nested inside reusable patterns is never missed. - Catalog drift surfaces on its own via the Unknown category. - Repeatable per environment, drafts on demand, and shareable as Excel for non-engineers. Note: figures in the sample console output above are illustrative.