root@construct:~/projects/kaizen-forge$
<-- back to /projects

Kaizen Forge

Site for my AI and software engineering practice

Next.js 15React 19TypeScriptVercel

>Context

Kaizen Forge is my software house and consultancy: a team of developers building software for clients. The site, at kaizenforge.tech, presents three services: AI consulting, software engineering and career coaching. It also explains how we work: fixed scope, timeline and price, in a six-stage method.

The main audience buys in Brazil, so Portuguese sits at the root of the domain. The other six languages serve people coming from abroad: English, Spanish, Japanese, German, Russian and Chinese.

I designed the site and I maintain it. The first commit is from 3 July 2026: the site started bilingual and gained the other five languages the same day. It runs in production on Vercel, and the latest commit in the repository is from 18 September 2026.

>High-level design

The site runs on Vercel. Both domains, registered with GoDaddy, use Vercel's nameservers, and vercel.json redirects www.kaizenforge.tech, kaizenforge.com.br and www.kaizenforge.com.br to kaizenforge.tech with a 308. On a visit, the next-intl middleware runs on the Edge runtime, reads the language prefix from the URL (the root is pt-BR) and rewrites the route to the [locale] segment. The page comes prerendered from Vercel's cache.

There is no database, API route or server-side state. Content lives in the 7 JSON catalogs and in the pages generated at build time, and the only state is the visitor's consent choice, kept in a browser cookie. There are no queues, crons or webhooks. The tracking layer accepts six providers through environment variables, but the production bundle checked on 22 Oct 2026 has none configured. The contact form is off too: contact goes through WhatsApp and email links.

Production deploys go out from a local session through the Vercel CLI. On GitHub, Actions runs CI on every pull request and on pushes to main and develop: lint with import boundaries, typecheck, tests, build, Storybook, Playwright end-to-end tests, Lighthouse and a bundle budget. A deploy workflow on Actions exists, but only as a manual trigger, and Dependabot handles dependency updates.

Hover or focus a piece: it lights up with its links.
BrowserLocal machineGitHubVercelContact1 Visitor3 Production deploy2 Banner + track()5 CI4 Repository7 DNS9 Locale middleware11 Static pages6 Dependabot8 Domains + redirects10 Deployment13 WhatsApp and email12 Locale catalogs
13 pieces12 links5 areas

Components

  • Domains and DNS :: kaizenforge.tech and kaizenforge.com.br use Vercel DNS, and the vercel.json rules send www and .com.br to kaizenforge.tech with a 308.
  • Locale middleware :: next-intl runs on the Edge runtime before every page, skipping api, _next and static files, serves the root as pt-BR and resolves /en, /es, /ja, /de, /ru and /zh to the language of each prefix.
  • Static pages :: Next.js 15 with React 19 generates the pages for all 7 languages at build time (generateStaticParams), and Vercel serves them from its cache.
  • Locale catalogs :: One JSON file per language, with the same 240 keys in all 7, which next-intl loads when it renders each page.
  • Consent and track() :: In the browser, a store built on EventTarget keeps the choice in a cookie and holds the track() function until the visitor accepts the banner.
  • Deploy :: Production is published with the Vercel CLI from a local session; the deploy workflow on GitHub Actions is left as a manual trigger.
  • CI on GitHub Actions :: Eight jobs on every pull request: lint with import boundaries, typecheck, tests, build, Storybook, end-to-end, Lighthouse and bundle budget.

>Trade-offs

The root always serves pt-BR, with no browser language detection.

Alternative rejected: Redirecting based on the browser's Accept-Language.

Why: The main audience is Brazilian buyers, and stable URLs are better for SEO.

Portuguese slugs only in pt-BR. The other six languages use the English slugs, such as /ai-consulting.

Alternative rejected: Translating every page slug into every language.

Why: It makes no difference for SEO and avoids maintaining six slug sets.

A hand-written consent store on top of the EventTarget API.

Alternative rejected: Using Zustand.

Why: The tracking layer loads on every page. A library for such a small store would add bundle weight, and the native API also works outside React.

Explicit consent by default: nothing is measured until the visitor chooses.

Alternative rejected: Treating the pending state as permission.

Why: It is the default that fits LGPD and GDPR. The cost showed up in the pre-launch review: with no banner, tracking was off for everyone, so the banner went in, in all 7 languages.

>Highlights

  • >7 languages live: the home page returned 200 in pt-BR, en, es, ja, de, ru and zh on 22 Oct 2026.
  • >240 message keys per language, the same set in all 7 catalogs.
  • >83 unit tests passing (Vitest, 6 files), measured on 22 Oct 2026.
  • >Lighthouse in CI with a minimum score of 1.0 for accessibility and SEO and 0.9 for performance.
  • >A 130 KB (gzip) first-load JavaScript budget for the home page, checked in CI.

>Fun facts

  • *The home page title gets cut by a katana: a blade of light sweeps across it, the two halves drift apart and an ember stays on the cut line. Pure CSS, no animation library.
  • *One of the pre-launch fixes was Russian number formatting: 5 000+, with a space.
Kaizen Forge | The Broad Way | Kinho.dev