Learn Tailwind CSS - Migration Strategies & Large-Scale Refactors
Episode 21 of 23

Learn Tailwind CSS - Migration Strategies & Large-Scale Refactors

This episode covers migrating large projects to Tailwind: incremental techniques from traditional CSS, monorepo considerations for sharing config and design tokens, and safe rollback and compatibility testing strategies.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

Migrating a large codebase to Tailwind is a multi-year project, not a weekend task. Episode 21 covers realistic strategies: incremental component-by-component conversion, monorepo architecture for sharing config and tokens, and rollback and compatibility testing to keep the journey safe.

The right mindset: migration isn't "replace all CSS today", it's "continuously reduce dependence on old CSS until the point where removing it becomes cheap". Every step must be reversible, measurable, and non-disruptive to users.

Incremental Migration Techniques

Start with New Components

The key to safety: all new components use Tailwind immediately. This stops the old CSS from growing from day one.

Setting up Tailwind alongside old CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

After that, Tailwind's output.css is imported alongside the old stylesheets. Nothing is removed — only added.

Convert Components One by One

Convert one component at a time. The recommended pattern: move utilities directly into markup, then delete the old CSS rule after the visuals are verified:

HTMLBefore and after conversion
<!-- Sebelum -->
<button class="btn btn-primary">Simpan</button>
 
<!-- Sesudah -->
<button class="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
  Simpan
</button>

For components with complex CSS, first translate into @apply in the components layer for a gradual transition — then set it aside later once the markup is clean.

Feature Flags for Migration

Run two versions of the UI simultaneously using a feature flag. Some users get the Tailwind version, the rest get the old one — compare metrics and bug reports before switching fully:

JSMigration flag on the server
const useTailwind = featureFlags.isEnabled(user.id, "ui-tailwind");
const variant = useTailwind ? "tailwind" : "legacy";

featureFlags.isEnabled(user.id, "ui-tailwind") gives you a path to test changes on a subset of users, while keeping the rest of the traffic on the stable version.

Monorepo Considerations

In a monorepo, config and tokens are shared across packages without duplication. Put the preset in its own package:

Monorepo structure
packages/
  ui/                -> komponen bersama
  tailwind-preset/   -> token + plugin bersama
apps/
  web/               -> aplikasi utama
  admin/             -> aplikasi admin

The preset is consumed by all applications:

JSPreset consumer in apps/web
module.exports = {
  presets: [require("@org/tailwind-preset")],
  content: [
    "./app/**/*.{js,ts,jsx,tsx}",
    "../../packages/ui/src/**/*.{js,ts,jsx,tsx}",
  ],
};

Notice content includes ../../packages/ui/src/**/* — without this, classes in shared components are never generated. The pattern content: ["../../packages/ui/src/**/*"] is the most commonly missed detail in monorepo setups.

Rollback and Compatibility Testing

A big migration needs a way home. A few safety nets:

  • Separate deploy: the Tailwind version and the old version are deployed together; rollback is just switching traffic.
  • Compat testing: test old and new components on the same browsers and screen sizes before deleting old CSS.
  • Visual baseline: keep screenshots of the old version for comparison — this uses Playwright from episode 20.

A simple compatibility test example:

Multi-browser compatibility test
npx playwright test --project=chromium --project=firefox --project=webkit

npx playwright test --project=chromium runs the whole suite across three browser engines at once. If the new look passes all projects and metrics don't decline, the component is considered ready.

Warning

Never delete old CSS wholesale in a single commit. Only remove it per component after its Tailwind version passes visual tests and runs stably in production for a while. Frequent small changes are safer than one big leap.

Conclusion

Episode 21 made large migrations feel safe: new components use Tailwind immediately, per-component conversion with flags, monorepo architecture for sharing presets, and continuous rollback and compatibility testing.

Key takeaways:

  • New components always use Tailwind; old CSS stops growing.
  • Convert one component at a time; remove old CSS after verification.
  • Feature flags allow testing on a subset of users.
  • Tailwind presets are shared across packages in a monorepo.
  • content must include the path to the shared component package.
  • Test across multiple browser engines and keep visual baselines for rollback.

Next, in episode 22 — the final episode — we'll cover production hardening & best practices: a final build-size checklist, CSP, SRI, caching, and accessibility audits, avoiding runtime Tailwind injection, and documenting conventions for your team.