ss @tailwindcss/vite
### Step 2: Vite Configuration
Vite's plugin architecture replaces PostCSS entirely. The `@tailwindcss/vite` plugin intercepts CSS processing during the build pipeline, eliminating the need for a separate PostCSS configuration file.
```typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwind from '@tailwindcss/vite'
export default defineConfig({
plugins: [
react(),
tailwind()
],
build: {
rollupOptions: {
output: {
manualChunks: undefined
}
}
}
})
Architecture Rationale: The manualChunks: undefined setting allows Rollup to automatically split vendor and application code based on import analysis. This prevents monolithic bundle generation and improves long-term caching efficiency. The plugin order matters: react() must precede tailwind() to ensure JSX transformation completes before CSS injection.
Step 3: CSS-First Theming
Tailwind v4 shifts theme configuration from JavaScript to CSS. The @theme directive registers custom properties that map directly to Tailwind's utility system. This approach guarantees that design tokens are resolved at the CSS layer, eliminating runtime JavaScript overhead.
/* src/styles/global.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
@import "tailwindcss";
@theme {
--color-canvas: #0B0F19;
--color-panel: #151B2B;
--color-panel-elevated: #1E2638;
--color-primary: #3B82F6;
--color-primary-hover: #2563EB;
--color-text-primary: #F8FAFC;
--color-text-secondary: #94A3B8;
--color-border: #2A3441;
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--radius-container: 0.75rem;
}
html {
background-color: var(--color-canvas);
color: var(--color-text-primary);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#app-root {
min-height: 100dvh;
display: grid;
grid-template-rows: auto 1fr auto;
}
Critical Implementation Detail: External @import statements must precede @import "tailwindcss". Tailwind v4 expands this directive into a full CSS cascade during compilation. If external fonts or resets appear after the directive, the browser's cascade resolution will ignore them, resulting in missing typography or broken layout inheritance.
Step 4: Data-Driven Component Architecture
Hardcoding content inside presentation components creates maintenance debt. A production-grade setup separates data definitions from UI logic using TypeScript interfaces and centralized data modules.
// src/data/platform-features.ts
export interface FeatureMetric {
identifier: string;
label: string;
description: string;
category: 'compute' | 'storage' | 'network';
}
export const platformFeatures: FeatureMetric[] = [
{
identifier: 'auto-scaling',
label: 'Autonomous Scaling',
description: 'Dynamic resource allocation based on real-time telemetry thresholds.',
category: 'compute',
},
{
identifier: 'edge-caching',
label: 'Edge Distribution',
description: 'Multi-region content replication with sub-50ms cache invalidation.',
category: 'network',
},
{
identifier: 'encrypted-storage',
label: 'Zero-Trust Persistence',
description: 'Client-side encryption with automated key rotation policies.',
category: 'storage',
},
];
// src/components/FeatureGrid.tsx
import { platformFeatures, FeatureMetric } from '../data/platform-features';
export function FeatureGrid() {
return (
<section className="grid grid-cols-1 md:grid-cols-3 gap-6 p-6">
{platformFeatures.map((feature: FeatureMetric) => (
<article
key={feature.identifier}
className="rounded-[var(--radius-container)] border border-[var(--color-border)] bg-[var(--color-panel)] p-5"
>
<h3 className="text-lg font-semibold text-[var(--color-text-primary)] mb-2">
{feature.label}
</h3>
<p className="text-sm text-[var(--color-text-secondary)] leading-relaxed">
{feature.description}
</p>
<span className="inline-block mt-4 px-2 py-1 text-xs font-medium rounded bg-[var(--color-panel-elevated)] text-[var(--color-primary)]">
{feature.category}
</span>
</article>
))}
</section>
);
}
Architecture Rationale: This pattern enables content updates without component recompilation. TypeScript enforces shape validation at compile time, preventing runtime undefined access. The separation also facilitates unit testing of data transformations independent of React rendering cycles.
Step 5: Client-Side Routing and SPA Fallback
React Router v7 manages client-side navigation. For static deployments behind CDNs, the routing layer must account for direct URL access and page refreshes.
// src/App.tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AppShell } from './components/AppShell';
import { DashboardView } from './views/DashboardView';
import { MetricsView } from './views/MetricsView';
import { SettingsView } from './views/SettingsView';
import { NotFoundFallback } from './components/NotFoundFallback';
export function Application() {
return (
<BrowserRouter>
<AppShell>
<Routes>
<Route index element={<DashboardView />} />
<Route path="metrics" element={<MetricsView />} />
<Route path="settings" element={<SettingsView />} />
<Route path="*" element={<NotFoundFallback />} />
</Routes>
</AppShell>
</BrowserRouter>
);
}
CDN Routing Requirement: Single-page applications serve a single index.html entry point. When a user requests /metrics directly, the origin server returns a 404 because the path doesn't map to a physical file. The CDN must intercept 403 and 404 responses and rewrite them to index.html with a 200 status code. This preserves the SPA routing context while satisfying HTTP semantics.
Pitfall Guide
1. CSS Import Order Violation
Explanation: Placing @import "tailwindcss" before external stylesheets or font declarations causes the browser to ignore subsequent imports due to CSS specification rules. Tailwind v4 expands the directive into a full cascade, and imports after it are treated as invalid.
Fix: Always position external @import statements, reset styles, and base typography rules above @import "tailwindcss". Validate build output with browser dev tools to confirm font loading.
2. CloudFront SPA Routing Blindspot
Explanation: Deploying a React SPA to S3 + CloudFront without custom error responses results in 403/404 pages on direct navigation or refresh. CloudFront treats non-root paths as missing objects rather than routing requests to the SPA entry point.
Fix: Configure CustomErrorResponses in your CloudFormation or Terraform deployment to map 403 and 404 errors to /index.html with a 200 response code. Set ErrorCachingMinTTL to 0 to prevent stale error pages during deployments.
3. Tailwind v4 Theme Scope Leakage
Explanation: Defining design tokens outside the @theme block or using arbitrary values inconsistently breaks utility generation. Tailwind v4 relies on CSS custom properties for theme resolution; arbitrary values bypass the theme system and increase bundle size.
Fix: Centralize all design tokens within @theme. Reference them using Tailwind's var(--color-*) utility syntax. Audit the codebase for hardcoded hex values and migrate them to theme variables.
4. Missing TypeScript Strict Mode
Explanation: Default TypeScript configurations often disable strict type checking, allowing implicit any types and unsafe property access. This defeats the primary advantage of adopting TypeScript and introduces runtime type errors that bypass compile-time validation.
Fix: Enable "strict": true in tsconfig.json. Configure "noUncheckedIndexedAccess": true to catch undefined array/object access. Run tsc --noEmit in CI pipelines to enforce type safety before bundling.
5. Vite Plugin Misconfiguration
Explanation: Omitting the React plugin or placing it after the Tailwind plugin causes JSX transformation to fail or CSS injection to occur before component compilation. Vite processes plugins sequentially, and dependency order directly impacts build stability.
Fix: Always register react() before tailwind(). Verify plugin execution order in vite.config.ts. Use vite --debug during development to inspect plugin hook execution and identify transformation failures.
6. Ignoring Error Caching TTL
Explanation: CloudFront caches error responses by default. If a deployment fails or a route is temporarily unavailable, users may receive cached 404/403 pages even after the origin recovers. This creates a false impression of persistent downtime.
Fix: Explicitly set ErrorCachingMinTTL: 0 in CDN error response configurations. Implement cache invalidation strategies during deployments to purge stale error states immediately.
7. Hardcoded UI Content
Explanation: Embedding text, labels, and metadata directly inside React components couples presentation with content. This pattern requires full component recompilation for minor copy changes and prevents content teams from updating UI without developer intervention.
Fix: Extract all repeatable content into typed data modules. Use interfaces to enforce shape validation. Map data arrays to components using .map() with stable key props. This enables content updates without touching presentation logic.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Static SPA deployment | Vite + Tailwind v4 + CloudFront | Native ESM dev server, CSS-first theming, edge caching | Low bandwidth, minimal compute |
| SEO-critical marketing site | Next.js or Remix | Server-side rendering, meta tag control, crawl optimization | Higher hosting cost, complex routing |
| Real-time dashboard | Vite + WebSockets + State library | Client-side interactivity, low latency updates | Moderate bandwidth, requires connection management |
| Legacy migration path | Vite with @vitejs/plugin-legacy | Backwards compatibility for older browsers | Increased bundle size, polyfill overhead |
Configuration Template
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwind from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwind()],
server: {
host: true,
strictPort: true
},
build: {
sourcemap: false,
rollupOptions: {
output: {
assetFileNames: 'assets/[name]-[hash][extname]',
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name]-[hash].js'
}
}
}
})
/* src/styles/global.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
@import "tailwindcss";
@theme {
--color-canvas: #0B0F19;
--color-panel: #151B2B;
--color-primary: #3B82F6;
--color-text-primary: #F8FAFC;
--color-text-secondary: #94A3B8;
--color-border: #2A3441;
--font-sans: 'Inter', system-ui, sans-serif;
}
html {
background-color: var(--color-canvas);
color: var(--color-text-primary);
font-family: var(--font-sans);
}
# CloudFormation Custom Error Response Snippet
CustomErrorResponses:
- ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
ErrorCachingMinTTL: 0
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
ErrorCachingMinTTL: 0
Quick Start Guide
- Initialize: Run
npm create vite@latest my-app -- --template react-ts and navigate into the directory.
- Install Dependencies: Execute
npm install react-router-dom tailwindcss @tailwindcss/vite.
- Configure Vite: Replace
vite.config.ts with the template above, ensuring plugin order matches the specification.
- Define Theme: Create
src/styles/global.css, place external imports above @import "tailwindcss", and register design tokens in @theme.
- Verify: Run
npm run dev to confirm HMR functionality, then npm run build to validate production output size and structure.