-
Simplicity = Maintainability
- No build steps, no dependency hell, no weird bundler configs.
- You understand every line of your code — no magic, no indirection.
- Easier onboarding for others (as long as the code is cleanly organized).
-
Performance
- No virtual DOM diffing, hydration, or bloated JS bundles.
- Initial load times are faster because you're not shipping half of npm to the browser.
-
Direct Browser Support
- Modern browsers are incredibly capable now. Features like
fetch,async/await, modules, and CSS Grid/Flexbox make life easier.
- Modern browsers are incredibly capable now. Features like
-
Better for Certain Kinds of Apps
- Internal tools, dashboards, data-heavy UIs, or admin panels don’t always need client-side routing or SSR.
- Tabulator is a great example of a powerful library that thrives in a no-framework setup.
-
You Control the Stack
- You pick the graphing library. You pick your CSS utility. No lock-in, no ecosystem churn.
-
Bootstrap Gets the Job Done
- It's not trendy, but it’s stable and familiar, and if you're not chasing bleeding-edge UI trends, it’s more than enough.
-
Componentization & Reusability
- Frameworks like React/ Vue shine when you need lots of composable, nested UI components with shared state and dynamic updates.
- You’ll need to roll your own patterns for components and state management — or just keep things flat and simple.
-
Tooling Ecosystem
- Frameworks come with built-in solutions (routing, form handling, SSR, etc.).
- With vanilla JS, you’ll selectively add libraries (like Chart.js or ApexCharts for graphs).
-
Community & Hiring Perception
- Yes, some devs may turn their nose up and say “where’s your SPA router or Vite config?”
- But real users (and smart devs) care more about usability and maintainability than whether you’re using the flavor-of-the-week stack.
-
Scalability Concerns
- For very large apps with lots of interactive components, it can become messy to manage state or keep UI updates consistent.
- That said, many apps never get that big, and thoughtful structure can go a long way.
-
Developer Experience
- You’ll miss out on some DX perks like hot module replacement, JSX syntax, or TypeScript integration unless you manually add them.
-
Structure Your Code Like Components (Manually)
- Use ES modules (
import/export) to break up logic and rendering functions. - Wrap UI chunks into small, reusable functions or classes.
- Use ES modules (
-
Use Modern CSS
- Learn CSS Grid/Flexbox and use utility classes (Tailwind is usable without a framework, or even just your own class helpers).
- Look into component-focused CSS like Web Components or just scoped styles via Shadow DOM.
-
Enhance With Good Libraries
- Graphing: Chart.js, ApexCharts, or Plotly work great with vanilla JS.
- State management: You might not even need one! But something like Observable patterns can be helpful for complex views.
-
Use a Lightweight Router if Needed
- If you ever want client-side routing, consider something tiny like Navigo.
-
Progressively Enhance
- Start simple, and add complexity only where the use case demands it.
- There’s a growing number of developers who write “frameworkless” or “vanilla-first” apps for performance, maintainability, and control.
- Sites like htmx, unpoly, and Alpine.js give modern interaction with minimal JS.
- Even GitHub and Basecamp (hey, DHH!) have talked about moving away from heavy JS frontends where not needed.
Awesome — let’s get you set up with a clean, modern vanilla JS module structure that scales well without needing React/Vue. This structure will give you:
✅ Code separation
✅ Component-style organization
✅ Easy readability and maintainability
✅ The ability to grow your app without losing control
Here’s a simple example:
/my-app
│
├── index.html
├── main.js # Your entry point
├── /components # Reusable UI chunks
│ ├── navbar.js
│ ├── chart.js
│ └── table.js
├── /services # API or data utilities
│ └── supabase.js
├── /utils # General helper functions
│ └── dom.js
├── /styles
│ └── style.css
In index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vanilla App</title>
<link rel="stylesheet" href="./styles/style.css">
</head>
<body>
<div id="app"></div>
<!-- Load JS entry point -->
<script type="module" src="./main.js"></script>
</body>
</html>import { renderNavbar } from './components/navbar.js';
import { renderChart } from './components/chart.js';
import { renderTable } from './components/table.js';
function init() {
renderNavbar();
renderChart();
renderTable();
}
init();import { createElement } from '../utils/dom.js';
export function renderTable() {
const app = document.getElementById('app');
const table = createElement('div', 'my-table-container');
table.innerHTML = `<p>This is where the table will go</p>`;
app.appendChild(table);
}export function createElement(tag, className = '', id = '') {
const el = document.createElement(tag);
if (className) el.className = className;
if (id) el.id = id;
return el;
}import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'https://your-project.supabase.co';
const supabaseKey = 'public-anon-key';
export const supabase = createClient(supabaseUrl, supabaseKey);
export async function fetchData(tableName) {
const { data, error } = await supabase.from(tableName).select('*');
if (error) throw error;
return data;
}body {
font-family: sans-serif;
margin: 2rem;
}
.my-table-container {
border: 1px solid #ccc;
padding: 1rem;
}- Use browser-native modules (
type="module") — modern browsers support them out of the box. - Split code based on responsibility (rendering vs logic vs API vs helpers).
- No bundlers required unless you want one — but you can easily add tools like Vite or Parcel later without changing your architecture.
- Use import maps (optional) to simplify path management.
- Store global app state in a single file if needed — just like Redux but simpler.
- No framework lock-in
- Native modules
- Real separation of concerns
- A “component-like” feel
- Readable code with no magical abstraction layers