WebZum Logo
WebZum

From Zero to Website Hero

Sign InSign Up
Back to Blog
tailwindssrperformanceinfrastructurestartup

We Compile Tailwind CSS at Build Time for AI-Generated HTML (No CDN Required)

WebZum Engineering•January 3, 2026•6 min read
We Compile Tailwind CSS at Build Time for AI-Generated HTML (No CDN Required)

TL;DR: Our AI generates HTML with Tailwind CSS classes. But generated websites need to work without JavaScript, CDN scripts, or build tools. We built a server-side Tailwind 4 compiler that extracts classes from AI-generated HTML, compiles only the CSS that’s actually used, integrates DaisyUI themes, and injects the result as an inline <style> tag. Zero layout shift. No external dependencies. Sub-50KB CSS for any site.

The Problem: AI Writes Tailwind, But Who Compiles It?

When you tell an AI to generate a webpage, it writes Tailwind classes naturally—flex, p-4, text-center, bg-gradient-to-r from-primary to-primary/80. The AI understands utility-first CSS.

But here’s the gap: Tailwind CSS needs a compilation step. Those class names mean nothing to a browser without the corresponding CSS rules.

Most people solve this with the Tailwind CDN script:

<script src="https://cdn.tailwindcss.com"></script>

This is terrible for production:

  • Blocks rendering until the script loads
  • Generates ALL possible CSS (megabytes of unused rules)
  • No DaisyUI support without additional config
  • JavaScript required → breaks without JS
  • Performance penalty → poor Core Web Vitals

We needed a different approach.

The Solution: Programmatic Tailwind 4 Compilation

Tailwind 4 introduced a programmatic compile() API. We use it server-side to compile only the classes present in our generated HTML:

import { compile } from 'tailwindcss';

export async function compileTailwindCss(
  classes: string[],
  options: TailwindCompilerOptions = {}
): Promise<string> {
  const compiler = await getCompiler(options.includePreflight, options.includeDaisyUI);

  // Only generates CSS for classes that exist in the HTML
  let css = compiler.build(classes);

  if (options.includeDaisyUI) {
    css += '\n' + getDaisyUIThemeCss(options.daisyUITheme);
  }

  return options.minify ? minifyCss(css) : css;
}

The key: compiler.build(classes) only generates CSS rules for the exact classes you pass in. A page using 200 Tailwind classes gets ~15KB of CSS. Not megabytes.

Class Extraction from AI-Generated HTML

First, we extract every Tailwind class from the generated HTML:

export function extractClassesFromHtml(html: string): string[] {
  const classesSet = new Set<string>();
  const CLASS_ATTR_REGEX = /(?:class|className)=["']([^"']+)["']/gi;

  let match;
  while ((match = CLASS_ATTR_REGEX.exec(html)) !== null) {
    match[1].split(/\s+/).forEach(cls => {
      if (cls.trim()) classesSet.add(cls.trim());
    });
  }

  return Array.from(classesSet);
}

This catches everything the AI might generate:

  • Standard utilities: flex, p-4, rounded-lg
  • Responsive variants: md:text-base, lg:grid-cols-3
  • State variants: hover:bg-blue-600, focus:ring-2
  • Arbitrary values: w-[200px], h-[calc(100vh-80px)]
  • Opacity modifiers: bg-primary/50, from-primary/80
  • Negative values: -mt-4, -translate-x-1/2

The DaisyUI Challenge

We use DaisyUI for theming—it gives us semantic color names like bg-primary, text-secondary, btn-accent. But DaisyUI extends Tailwind’s color palette, which means Tailwind needs to know about DaisyUI’s colors to compile classes like bg-primary/80.

The solution: load DaisyUI as a proper Tailwind plugin at compile time:

async function loadModule(id: string, base: string): Promise<any> {
  if (id === 'daisyui') {
    const daisyui = await import('daisyui');
    return { path: 'daisyui', base, module: daisyui.default };
  }
}

// Compile with DaisyUI plugin
const baseCss = `@import "tailwindcss";\n@plugin "daisyui";`;
const compiler = await compile(baseCss, {
  base: projectRoot,
  loadModule,
});

Now when the AI writes from-primary/80, Tailwind knows primary is a valid color and generates the correct opacity modifier CSS using color-mix():

.from-primary\/80 {
  --tw-gradient-from: color-mix(in oklab, var(--color-primary) 80%, transparent);
}

Theme CSS Variables

DaisyUI themes are injected as CSS custom properties on the [data-theme] selector:

[data-theme="autumn"] {
  --color-primary: #8C0327;
  --color-secondary: #D85251;
  --color-accent: #D59B6C;
  --color-base-100: #F5E9DC;
}

We support 28 DaisyUI themes. The AI picks the best theme for each business during the brand strategy step—but that’s another blog post.

Plugin Styles: The Runtime Problem

We inject plugins at runtime—chatbot widgets, analytics, banners. These plugins use Tailwind classes too. But they’re added after the initial HTML is compiled.

Solution: pre-compile plugin classes at build time:

export function getPluginTailwindClasses(): string[] {
  const allClasses = new Set<string>();

  // Instantiate each plugin and extract its classes
  const chatbotGenerator = new ChatbotGenerator('Sample Business');
  const chatbotHtml = chatbotGenerator.generateWidget();
  extractClassesFromHtml(chatbotHtml).forEach(cls => allClasses.add(cls));

  return Array.from(allClasses).sort();
}

Since plugin HTML is deterministic (same classes every time), we can extract and compile their styles ahead of time. The compiled CSS includes everything plugins will ever need.

The Full Pipeline

AI-Generated HTML (Header + Pages + Footer)
         │
         ├── extractClassesFromHtml() → Set of unique classes
         │
Plugin HTML (Chatbot, Banner, etc.)
         │
         ├── getPluginTailwindClasses() → Known plugin classes
         │
         └─── All classes combined
                    │
         compileTailwindCss(classes, {
           includeDaisyUI: true,
           daisyUITheme: 'autumn',
           minify: true
         })
                    │
         Inline <style> tag in final HTML

The output is a complete HTML page with zero external CSS dependencies:

<!DOCTYPE html>
<html lang="en" data-theme="autumn">
<head>
  <style>
    /* Server-compiled Tailwind + DaisyUI (~20-40KB minified) */
    @layer utilities { ... }
    [data-theme="autumn"] { --color-primary: #8C0327; ... }
  </style>
  <link rel="stylesheet" href="./styles.css"> <!-- Just fonts + base styles -->
</head>

CSS Cascade Layers

We use @layer base for our global styles so Tailwind utilities always win:

@layer base {
  body {
    font-family: var(--font-secondary);
    line-height: 1.6;
  }
  header {
    position: sticky;
    top: 0;
    z-index: 50;
  }
}

This ensures that when Tailwind generates .z-40, it overrides the base z-index: 50 on header—because unlayered styles (Tailwind utilities in @layer utilities) beat @layer base in the cascade.

Caching

Compiling Tailwind isn’t free. We cache the compiler instance:

const compilerCache = new Map<string, Compiler>();

// Cache key includes preflight and DaisyUI flags
const cacheKey = `preflight:${includePreflight}:daisyui:${includeDaisyUI}`;

First compilation: ~200ms. Subsequent compilations with the same config: ~5ms (just compiler.build(classes)).

Results

Metric CDN Script Our SSR Approach
CSS Size 300KB+ (all utilities) 15-40KB (used only)
First Paint Blocked by JS Immediate
Layout Shift Yes (CSS loads async) Zero
JS Required Yes No
DaisyUI Support Manual config Built-in
Core Web Vitals Poor Excellent

What Made This Hard

  1. Opacity modifiers on DaisyUI colors. bg-primary/80 requires DaisyUI to be loaded as a plugin, not just CSS variables. Took us a week to figure out.
  2. Plugin class extraction. Runtime plugins add HTML after compilation. Pre-extracting their classes was the only reliable solution.
  3. CSS Cascade Layers. Getting the specificity right between base styles, Tailwind utilities, and DaisyUI components required careful @layer management.

Try It

Every website on WebZum uses server-compiled Tailwind CSS. View source on any generated site—you’ll see a clean <style> tag with only the CSS that page actually uses. No CDN. No JavaScript. No layout shift. Just fast, styled HTML.

Ready to Build Your Website?

Join hundreds of businesses using WebZum to create professional websites in minutes, not weeks.

Get Started Free
Live in 5 minutesNo credit card required
Home•Free Tools•Blog•Directory•About•Agencies•Partners
FAQ•Privacy•Terms•© 2026 WebZum