Building Fast Websites Without the Framework Fatigue
Modern web development doesn't require React for everything. Here's how I choose the right tools for the job, from vanilla HTML to progressive enhancement.

The Default Should Be Simple
I've been building websites for over a decade, and the most important lesson I've learned is this: start with the simplest thing that works. Not the most impressive tech stack, not what's trending on Hacker News—just HTML, CSS, and vanilla JavaScript until you have a concrete reason to reach for more.
This isn't a manifesto against frameworks. I use them regularly. But I've watched too many projects collapse under the weight of tooling choices made on day one, before anyone understood what they were actually building.
When I Reach for Vanilla HTML
Most of my freelance projects start as static HTML. Marketing sites, landing pages, documentation—these don't need state management or client-side routing. They need fast load times and good SEO.
Here's my typical starting point:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Project Name</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<main>
<!-- Content here -->
</main>
<script src="/js/main.js" defer></script>
</body>
</html>
No build step. No node_modules directory consuming gigabytes. Just files I can edit and see update immediately in the browser.
For styling, I write CSS custom properties for theming and use modern features like Grid and clamp() for responsive layouts. The browser is remarkably capable now:
:root {
--spacing-unit: clamp(1rem, 2vw, 1.5rem);
--max-width: 70ch;
}
main {
max-width: var(--max-width);
margin-inline: auto;
padding: var(--spacing-unit);
}
Progressive Enhancement Still Works
When I need interactivity, I add it progressively. A contact form works without JavaScript—it just posts to a server endpoint. Then I enhance it:
const form = document.querySelector('form');
if (form) {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const data = new FormData(form);
try {
const response = await fetch(form.action, {
method: 'POST',
body: data
});
if (response.ok) {
form.innerHTML = '<p>Thanks! I\'ll be in touch soon.</p>';
}
} catch (err) {
// Form still works, just falls back to page reload
form.submit();
}
});
}
This pattern—make it work, then make it better—keeps projects shippable at every stage.
When I Reach for a Framework
I pull in a framework when the interaction model demands it. Admin dashboards, real-time collaboration tools, apps with complex client-side state—these benefit from React, Vue, or Svelte.
My current preference is Svelte for new projects. The component model is clean, the compiled output is small, and I'm not fighting with hooks or reactivity systems:
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
<style>
button {
padding: 0.5rem 1rem;
font-size: 1rem;
}
</style>
For projects that need server-side rendering or static generation, I reach for Astro. It lets me write components in whatever framework I want (or none), then ships minimal JavaScript to the client. Most pages in an Astro site are just HTML and CSS.
The Build Tool Question
When a project needs a build step, I keep it minimal. For simple bundling and minification, esbuild handles everything:
// build.js
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/main.js'],
bundle: true,
minify: true,
outfile: 'dist/main.js',
target: 'es2020'
});
No webpack config spanning hundreds of lines. No plugin ecosystem to maintain. Just fast bundling that works.
For CSS, I use PostCSS with autoprefixer when I need it, but modern CSS has reduced that need significantly. Features like :has(), container queries, and cascade layers are widely supported as of 2026.
Deployment and Performance
Static sites go on Netlify or Cloudflare Pages. Both offer excellent DX with git-based deployments and automatic HTTPS. For anything server-side, I use Fly.io or Railway—platforms that let me deploy Docker containers without managing infrastructure.
Performance matters more than tooling. I optimize images (WebP with PNG fallbacks), lazy-load below-the-fold content, and measure everything with Lighthouse. A well-optimized static site will outperform a poorly-optimized React app every time.
What This Looks Like in Practice
My typical project structure:
project/
├── src/
│ ├── css/
│ ├── js/
│ └── index.html
├── public/
│ └── images/
└── package.json (if needed)
I add complexity only when the project demands it. A blog gets Eleventy or Hugo. An app with real-time features gets a framework. A marketing site stays static.
The Real Framework
The framework that matters most isn't React or Vue—it's the mental model for choosing the right tool for each job. Start simple. Add complexity deliberately. Ship working software.
Most web projects don't need a SPA. They need fast-loading pages that work on slow connections and old devices. You can build those with HTML, CSS, and small amounts of JavaScript. Everything else is optional.