Building Fast Websites Without a Framework
Modern browsers provide everything you need to build performant, interactive websites. Here's how vanilla JavaScript and web standards can replace most framework boilerplate.

Why Skip the Framework?
Every web project doesn't need React, Vue, or Svelte. For content-heavy sites, admin dashboards, or landing pages, frameworks add bundle size and complexity that slow development and runtime performance. Modern JavaScript and CSS have evolved enough that vanilla approaches often ship faster and maintain better.
I've shipped production sites both ways. Frameworks excel at complex state management and real-time interfaces, but most projects don't need that. A typical marketing site or blog renders static content with light interactivity—exactly what the platform handles natively.
What You Get for Free
Browsers in 2026 support features that used to require libraries:
- ES modules:
importandexportwork natively, no bundler required for development - Template literals: String interpolation and multi-line templates replace most templating engines
- Fetch API: Clean HTTP requests without axios or similar
- CSS Grid and Flexbox: Complex layouts without framework component systems
- Custom elements: Web components for reusable UI without JSX
- CSS custom properties: Theme variables without preprocessors
The gap between vanilla web and framework features has narrowed significantly.
A Practical Example
Here's a real component pattern I use—a tabbed interface without any framework:
<div class="tabs" data-tabs>
<nav>
<button data-tab="overview" class="active">Overview</button>
<button data-tab="details">Details</button>
<button data-tab="specs">Specs</button>
</nav>
<div data-panel="overview" class="active">
<p>Overview content here</p>
</div>
<div data-panel="details" hidden>
<p>Details content here</p>
</div>
<div data-panel="specs" hidden>
<p>Specs content here</p>
</div>
</div>
class Tabs {
constructor(element) {
this.root = element;
this.buttons = element.querySelectorAll('[data-tab]');
this.panels = element.querySelectorAll('[data-panel]');
this.buttons.forEach(button => {
button.addEventListener('click', () => this.activate(button.dataset.tab));
});
}
activate(tabId) {
this.buttons.forEach(btn => btn.classList.toggle('active', btn.dataset.tab === tabId));
this.panels.forEach(panel => {
const isActive = panel.dataset.panel === tabId;
panel.toggleAttribute('hidden', !isActive);
panel.classList.toggle('active', isActive);
});
}
}
document.querySelectorAll('[data-tabs]').forEach(el => new Tabs(el));
This weighs under 500 bytes minified. The equivalent React component would pull in the entire runtime—roughly 140KB minified. For users on slow connections, that's the difference between instant interaction and waiting.
State Management
Simple state doesn't need Redux or Zustand. JavaScript objects work fine:
const state = {
user: null,
notifications: [],
setUser(userData) {
this.user = userData;
this.render();
},
addNotification(message) {
this.notifications.push({ id: Date.now(), message });
this.render();
},
render() {
// Update DOM based on current state
document.getElementById('username').textContent = this.user?.name || 'Guest';
updateNotificationBadge(this.notifications.length);
}
};
For reactive updates, Proxy objects can intercept changes and trigger renders automatically, mimicking Vue's reactivity without the framework weight.
When Frameworks Make Sense
This isn't anti-framework. Use them when you need:
- Real-time collaboration: Google Docs-style interfaces with operational transforms
- Complex data flows: Deeply nested component hierarchies sharing state
- Large teams: Framework conventions help coordinate multiple developers
- Heavy client-side routing: SPAs with dozens of routes and lazy loading
But a blog with a newsletter signup? A restaurant menu site? A portfolio? Those don't need virtual DOM diffing.
Build Process
Vanilla doesn't mean zero tooling. I use:
- esbuild: Bundle and minify for production, development server for HMR
- PostCSS: Autoprefixer and CSS nesting when helpful
- Prettier: Code formatting
No complex webpack configs, no framework CLI. The entire build setup fits in a dozen lines of configuration.
// build.js
import esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/main.js'],
bundle: true,
minify: true,
sourcemap: true,
outdir: 'dist',
target: ['es2020'],
});
Performance Results
A recent client project switched from Next.js to vanilla HTML/CSS/JS. Metrics:
- First Contentful Paint: 0.4s → 0.2s
- Time to Interactive: 2.1s → 0.5s
- Total bundle size: 347KB → 23KB
- Lighthouse score: 78 → 99
Users on 3G connections finally had a usable experience. The code became simpler, fewer abstractions to explain.
The Mental Shift
Frameworks make you think in components and lifecycle hooks. Vanilla makes you think in progressive enhancement:
- Build the HTML that works without JavaScript
- Add CSS that works without JavaScript
- Layer JavaScript for interactivity
This approach naturally creates resilient interfaces. If the JS fails to load, the site still functions.
Conclusion
Frameworks solve real problems for certain projects. But they're not a default choice anymore. The platform has caught up. For many sites, vanilla JavaScript delivers better performance, simpler maintenance, and faster load times.
Next project, try skipping the framework. You might ship something faster in both senses of the word.