Why we rebuilt from scratch
Build444's previous site was functional. It worked. But it had accumulated the kind of tech debt that makes every new feature take three times longer than it should.
We needed bilingual support (English and Danish). We wanted to sell services directly through the site with Stripe. And honestly, we wanted to dogfood our own stack. If we tell clients to invest in fast, modern websites, ours should be one.
So in early 2026, we tore it down and rebuilt on Next.js 16, React 19, Tailwind CSS v4, and Turbopack. This is the build log. The wins, the bugs, and the things we would do differently.
The stack
Next.js 16.1.6 with the App Router. We went all-in on server components. Most of our pages have zero client-side JavaScript unless they need interactivity. The shop, checkout, and form pages are client components. Everything else is server-rendered.
React 19. The new use hook and server actions simplified a lot of our data fetching. We still use useEffect in a few places, but far less than before.
Tailwind CSS v4.1.18. The new CSS-first configuration is cleaner than the old JavaScript config. But the migration was not smooth. More on that below.
Turbopack. Next.js 16 defaults to Turbopack for the dev server. Hot module replacement is fast. Build times dropped from 12 seconds to under 2. But Turbopack has edge cases that cost us real debugging time.
Stripe SDK 20.3.1. We handle checkout server-side with Stripe's latest SDK. Tax ID collection and automatic invoice creation are built in. The webhook handler forwards events to our n8n automation workflows.
Turbopack: fast, but watch the edges
Turbopack replaced Webpack as the default dev bundler in Next.js 16. For most things, it just works. Pages reload in milliseconds. The developer experience is noticeably better.
Then we hit the CSS bug.
The CSS stripping problem
When we added new CSS classes to our globals.css, multi-line formatted rules would silently disappear. No error. No warning. The styles just were not there.
We spent two days on this. Checked the browser inspector. Checked the build output. Added !important in desperation. Nothing.
The fix turned out to be absurd: write new CSS rules as single-line declarations.
/* This gets stripped by Turbopack */
.dropdown-overlay {
display: flex;
gap: 8px;
background: #faf9f6;
}
/* This works fine */
.dropdown-overlay { display: flex; gap: 8px; background: #faf9f6; }
Existing multi-line rules were not affected. Only newly added ones during hot module replacement. @keyframes blocks and font-family: var(...) in new rules also caused stripping. We switched to hardcoded values instead of CSS variables for new overlay and dropdown rules.
Restarting the dev server did not fix it. Only the single-line format did. We filed an issue. As of February 2026, it is still open.
The module resolution issue
Tailwind v4 uses @import "tailwindcss" as its entry point. This works with Webpack. With Turbopack, it fails. Turbopack's enhanced-resolve does not support the style condition in package exports.
The fix: use @import "tailwindcss/index.css" instead. Simple, once you know it. Maddening when you do not.
Bilingual architecture without an i18n library
We needed English and Danish. Most guides recommend next-intl or next-i18next. We tried both. They added complexity, bundle size, and configuration overhead that did not match our use case.
We have two languages. Not twenty. So we built it ourselves.
Middleware rewrites
A small middleware file catches incoming requests and rewrites paths based on a language cookie or the Accept-Language header:
/aboutserves English/da/aboutserves Danish- First-time visitors get detected by browser language
The middleware is 47 lines. It handles everything. No library needed.
The data-lang CSS approach
For components that need different styling per language (Danish words are longer than English ones), we use a data-lang attribute on the HTML element and target it in CSS:
[data-lang="da"] .hero-title { font-size: 2.2rem; }
[data-lang="en"] .hero-title { font-size: 2.5rem; }
No JavaScript. No re-renders. Just CSS doing what CSS does.
Content files per locale
Blog posts, service descriptions, and product copy live in separate files per language: en.mdx and da.mdx. The blog system reads the right file based on the URL locale. No translation keys. No JSON dictionaries. Just write the content in each language.
This approach would not scale to 10 languages. For two, it is simpler than any library we tried.
Performance results
We ran Lighthouse audits before and after the rebuild. The numbers speak for themselves.
| Metric | Old site | New site | |--------|----------|----------| | Performance | 67 | 98 | | Accessibility | 82 | 100 | | Best Practices | 78 | 100 | | SEO | 89 | 100 | | LCP | 3.8s | 1.1s | | CLS | 0.18 | 0.01 | | INP | 380ms | 45ms |
Largest Contentful Paint dropped from 3.8 seconds to 1.1. That is a direct result of server components. The old site loaded a 240KB JavaScript bundle before it could render the hero section. The new site sends HTML.
Cumulative Layout Shift went from 0.18 (failing) to 0.01 (passing with room to spare). We achieved this by setting explicit dimensions on every image and using CSS aspect-ratio for responsive containers.
Interaction to Next Paint improved because most pages have no client-side JavaScript at all. There is nothing to block the main thread.
The shop: 30+ products, one checkout
We built a shop with 30+ products across 7 categories. Each category has a colour system defined in a single TypeScript file. Products range from one-time SEO audits to monthly retainers.
The checkout flow uses Stripe's server-side SDK. No Stripe.js on the client. The customer clicks "Buy," we create a Checkout Session server-side, and redirect to Stripe's hosted page. This keeps our bundle small and our PCI compliance simple.
After payment, Stripe sends a webhook to our API route, which forwards the event to n8n for fulfillment automation. The SEO report product triggers a 48-node workflow that runs automated analysis and delivers a PDF report via email.
What we would do differently
Start with Turbopack testing on day one. We built most of the CSS with Webpack, then switched to Turbopack late in the project. That is when the CSS bugs appeared. If we had started with Turbopack, we would have discovered the single-line workaround earlier and saved two days.
Use CSS modules instead of global classes for new components. Our globals.css is 800+ lines. CSS modules would have given us better scoping and avoided the Turbopack stripping issue entirely.
Set up visual regression testing. We caught most layout bugs manually. A tool like Playwright's screenshot comparison would have caught the CLS issues faster, especially across the two language variants where text length differs.
Plan the bilingual content pipeline earlier. Writing content in two languages takes twice as long as one language. We underestimated the time needed for the Danish versions. Next time, we would budget for translation from the start, not bolt it on after the English content is done.
Was it worth it?
Next.js 16 with React 19 and Turbopack is a real performance upgrade. Our Lighthouse scores went from mediocre to near-perfect. Server components eliminated most of our client-side JavaScript. The development experience is faster.
But the tooling is still young. Turbopack has real bugs. Tailwind v4's import syntax does not work out of the box. You will hit edge cases that are not documented yet.
If you are starting a new project in 2026, this stack is the right choice. If you are migrating an existing one, budget extra time for the Turbopack quirks. They are solvable, but they are not obvious.
Our site is now fast, bilingual, and sells products directly through Stripe. The rebuild took six weeks. For what we got out of it, that was time well spent.
