Mark Ku's Blog

Context

Recently, we noticed that the bloated Bootstrap CSS framework was causing an average drop of 5-10 points per page in our Google Lighthouse scores. Consequently, we planned to remove Bootstrap. Since we were only using Bootstrap's CSS and not its JavaScript components, the replacement process was relatively straightforward.

However, we have a large number of pages. Refactoring the grid layout on every page to use Tailwind's grid system would have been very time-consuming and would have increased the risk of breaking pages. Therefore, we decided to postpone the page refactoring to a later phase.

Why Use Tailwind

  • Lightweight
  • Fast for building layouts
  • Rich set of selectors
  • Easy to extend
  • Saves time writing CSS and creating helper classes
  • A mobile-first CSS framework

Tools Used for this Tailwind Conversion

First, for batch modifications, we wrote a Powershell script named convert-boostrap-to-tailwindcss.ps1

# 設定搜尋的目錄路徑
$directoryPath = "E:\VisualStudioProject\Apps\NextJS"

# 設定需要替換的字詞對應
$replacements = @{
    'd-none' = 'hidden'
    'd-block' = 'block'
    'd-flex' = 'flex'
    'd-inline-flex' = 'inline-flex'
    'flex-column' = 'flex-col'
    'justify-content-end' = 'justify-end'
    'align-items-start' = 'items-start'
    'align-items-center' = 'items-center'
    'align-items-end' = 'items-end'
    'align-items-stretch' = 'items-stretch'
    'justify-content-center' = 'justify-center'
    'justify-content-start' = 'justify-start'
    'justify-content-between' = 'justify-between'
    'justify-content-around' = 'justify-around'
    'position-absolute' = 'absolute'
    'd-xl-none' = 'xl:hidden'
    'd-xl-block' = 'xl:block'
    'd-md-block' = 'md:block'
    'd-md-none' = 'md:hidden'
    'w-100' = 'w-full'
    'h-100' = 'h-full'
    'p-3' = 'p-4'
    'p-4' = 'p-6'
    'p-5' = 'p-12'
    'px-3' = 'px-4'
    'px-4' = 'px-6'
    'px-5' = 'px-12'
    'py-3' = 'py-4'
    'py-4' = 'py-6'
    'py-5' = 'py-12'
    'pt-3' = 'pt-4'
    'pt-4' = 'pt-6'
    'pt-5' = 'pt-12'
    'pb-3' = 'pb-4'
    'pb-4' = 'pb-6'
    'pb-5' = 'pb-12'
    'ps-3' = 'ps-4'
    'ps-4' = 'ps-6'
    'ps-5' = 'ps-12'
    'p-md-4' = 'md:p-6'
    'p-lg-4' = 'lg:p-6'
    'py-md-4' = 'md:py-6'
    'pt-lg-5' = 'md:p-12'
    'pb-md-4' = 'md:pb-6'
    'ps-lg-3' = 'lg:ps-4'
    'pe-md-5' = 'md:pe-12'
    'm-3' = 'm-4'
    'm-4' = 'm-6'
    'm-5' = 'm-12'
    'mx-3' = 'mx-4'
    'mx-4' = 'mx-6'
    'mx-5' = 'mx-12'
    'my-3' = 'my-4'
    'my-4' = 'my-6'
    'my-5' = 'my-12'
    'mt-3' = 'mt-4'
    'mt-4' = 'mt-6'
    'mt-5' = 'mt-12'
    'mb-3' = 'mb-4'
    'mb-4' = 'mb-6'
    'mb-5' = 'mb-12'
    'ms-3' = 'ms-4'
    'ms-4' = 'ms-6'
    'ms-5' = 'ms-12'
    'my-md-3' = 'md:my-4'
    'mt-md-5' = 'md:mt-12'
    'mt-lg-5' = 'md:lg-12'
    'fs-1' = 'text-6xl'
    'fs-2' = 'text-5xl'
    'fs-3' = 'text-4xl'
    'fs-4' = 'text-3xl'
    'fs-5' = 'text-2xl'
    'fs-6' = 'text-xl'
    'fw-bold' = 'font-bold'
    'fw-lighter' = 'font-[lighter]'
    'fw-light' = 'font-light'
    'fw-semibold' = 'font-semibold'
    'col-12' = 'w-full'
    'col-md-4' = 'md:w-1/3'
    'col-6' = 'w-1/2'
    'col-lg-4' = 'lg:w-1/3'
    'col-lg-2' = 'lg:w-1/6'
    'col-sm-6' = 'sm:w-1/2'
    'col-sm-4' = 'sm:w-1/3'
    'col-3' = 'w-1/4'
    'col-4' = 'w-1/3'
    'col-lg-6' = 'lg:w-1/2'
    'col-lg-3' = 'lg:w-1/4'
    'col-md-8' = 'md:w-2/3'
    'col-xl-5' = 'xl:w-5/12'    
    'col-md-6' = 'md:w-1/2'
    'col-md-10' = 'md:w-5/6'
    'col-md-12' = 'md:w-full'
    'col-lg-5' = 'lg:w-5/12'
    'col-lg-7' = 'lg:w-7/12'
    'col-lg-8' = 'lg:w-2/3'
    'col-lg-9' = 'lg:w-3/4'
    'col-sm-8' = 'sm:w-2/3'
    'col-sm-9' = 'sm:w-3/4'
    'col-sm-12' = 'sm:w-full'
    'col-1' = 'w-1/12'
    'col-2' = 'w-1/6'
    'col-5' = 'w-5/12'
    'col-7' = 'w-7/12'
    'col-8' = 'w-2/3'
    'col-9' = 'w-3/4'
    'col-11' = 'w-11/12'
    'col-md-5' = 'md:w-5/12'
    'col-md-7' = 'md:w-7/12'
    'col-xl-6' = 'xl:w-1/2'
    'col-xl-2' = 'xl:w-1/6'
    'col-sm-offset-1' = 'sm:ml-1/12'
	// 依據你們專案用到的,可以自己增加... 
}

Set-Location -Path $directoryPath

Get-ChildItem -Recurse -Filter '*.tsx' | ForEach-Object {
    $escapedFileName = $_.FullName -replace '\[(.*?)\]', '`[$1`]'
    $content = Get-Content $escapedFileName -Raw

    $replacements.GetEnumerator() | ForEach-Object {
        $content = $content -replace "(?<!\!)$($_.Key)", $_.Value
    }
    
    [System.IO.File]::WriteAllText($_.FullName, $content)
}

pause

Manual Replacements Needed

container => 'container mx-auto

// bootstrap 的 container 比較特別,因此需要在 global scss 額外宣告 container
.container {
    width: 100%;
    margin-right: auto;
    margin-left: auto;
}

@media (min-width: 576px) {
    .container {
        max-width: 540px;
    }
}
@media (min-width: 768px) {
    .container {
        max-width: 720px;
    }
}
@media (min-width: 992px) {
    .container {
        max-width: 960px;
    }
}
@media (min-width: 1200px) {
    .container {
        max-width: 1140px;
    }
}
@media (min-width: 1400px) {
    .container {
        max-width: 1320px;
    }
}

Classes like row, due to potential naming conflicts, required manual replacement with "flex flex-wrap".

Classes like btn, close, dropdown-toggle, collapse, modal-header, and modal-body all had to be changed manually.

Some default font sizes and spacing that were misaligned.

Finally

Although switching technologies is always challenging, it's a worthwhile endeavor when you consider the long-term benefits for performance and maintainability. If you're also considering such an upgrade, we hope this helps you solve your problems.

Author

Mark Ku

擁有 10+ 年經驗的資深軟體工程師,現為 AI 應用 Builder,專注於大型平台架構與簡化複雜系統設計,從電商系統到訂閱與收費平台,結合 AI Agent、AI 整合與自動化開發,打造高效率且可持續演進的產品技術基礎。Read More

Found this useful?

The author's free tools, daily podcasts and newsletter are all here.

Mark Ku · This article is licensed under CC BY 4.0. Credit the author and link back to the original when reusing it.

Comments

Subscribe to Newsletter

Subscribe to get new posts delivered instantly — never miss a tech share.

By submitting, you agree to receive emails. You can anytime.

Popular Posts

View all
Mark Ku
··602

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution
Mark Ku
··490

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.
Mark Ku
··333

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki
Mark Ku
··264

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning
Mark Ku
··221

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1
Mark Ku
··215

Setting Up Samba on Ubuntu to Share Folders with Windows 11

Setting Up Samba on Ubuntu to Share Folders with Windows 11
用 Powershell 寫了一個工具, 將幾百頁 Bootstrap 的舊專案批量轉換到 Tailwind css - Mark Ku's Tech Notes