June 2026
Why I Replaced React with Alpine.js for Smaller Projects
Discover why Alpine.js is a lighter, simpler alternative to React for small projects without sacrificing functionality
A few years ago, I found myself building a small internal dashboard for a client in Zagreb. The requirements were simple: a dynamic table, some interactive filters, and a live status indicator. My first instinct, born from years of habit, was to reach for React. I set up the tooling, installed the dependencies, and within an hour I had a functional prototype. But I also had a node_modules folder bigger than the entire application logic, a build step, and a lingering feeling that I had used a sledgehammer to crack a walnut. That was the day I started looking for something lighter, and I found Alpine.js.
Since then, I have made a deliberate shift. For smaller projects—internal tools, marketing microsites, simple landing pages with interactive elements—I now reach for Alpine.js first. React remains my go-to for complex, state-heavy applications, but the gap between what these two tools offer for small-scale work is far wider than most developers admit. Let me explain why.
The Real Cost of React in a Small Project
The Tooling Tax
When you start a React project today, you are rarely just writing React. You are writing JSX, which means you need a transpiler. You are likely using a bundler like Vite or Webpack. You probably have ESLint, Prettier, and a testing framework configured. For a team of five working on a large SaaS product, this setup is a sensible investment. For a single developer building a contact form with a modal, it is overhead.
Every time I add a new dependency to a small React project, I am not just adding code. I am adding potential breaking points, update cycles, and cognitive load. Alpine.js, by contrast, is a single script tag. You drop it into your HTML file, and you start writing code immediately. No npm install, no package.json, no build step. For a small project, that difference is not just convenience—it is speed.
The Component Mental Model
React forces you to think in components. This is powerful for large applications, but for a small project, it often leads to premature abstraction. I have seen developers create a Button component, a Modal component, and a FormField component for a single-page tool that will be used by three people. The component tree becomes a barrier rather than an aid.
Alpine.js encourages a different approach. You write your logic directly in the HTML using x-data, x-on, and x-bind directives. The state lives right where it is used. There is no prop drilling, no context providers, no custom hooks. For a small project, this directness is liberating. Your HTML file becomes the single source of truth, and you can see the entire interaction of a page element in one glance.
When Alpine.js Shines Brightest
Marketing Sites and Landing Pages
I recently built a landing page for a small consultancy in Split. The page needed a sticky header that changed color on scroll, a testimonial carousel, and a FAQ accordion. With React, I would have set up a project, created multiple files, and imported a carousel library. With Alpine.js, I wrote the entire page in a single HTML file. The sticky header was x-data="{ scrolled: false }" with an x-init that added a scroll listener. The carousel was a few lines of x-data with x-show directives. The FAQ was a simple x-data toggle.
The client was thrilled because the page loaded instantly. There was no JavaScript bundle to download. The Alpine.js library itself is about 10 KB minified and gzipped. The interactivity was reactive, smooth, and required zero build tools. For a marketing site where performance and simplicity matter more than complex state management, this is a clear win.
Internal Tools and Dashboards
Internal tools are often the worst candidates for heavy frameworks. They are built quickly, used by a small team, and frequently updated. The overhead of a formal React setup often slows down iteration. With Alpine.js, I can prototype a dashboard in an afternoon. The x-data object holds the state, x-for loops render lists, and x-model binds form inputs directly.
One example: a simple order management view for a local retail chain. The requirements were a table of orders, a search input, and a status dropdown filter. In Alpine.js, the entire logic was:
<div x-data="{ orders: [], search: '', status: 'all' }">
<input x-model="search" placeholder="Search orders...">
<select x-model="status">
<option value="all">All</option>
<option value="pending">Pending</option>
<option value="shipped">Shipped</option>
</select>
<template x-for="order in orders.filter(o =>
(o.name.includes(search) || o.id.includes(search)) &&
(status === 'all' || o.status === status)
)">
<div>
<span x-text="order.id"></span>
<span x-text="order.name"></span>
<span x-text="order.status"></span>
</div>
</template>
</div>
No Redux. No React Query. No Axios. Just Alpine.js and a simple fetch call in x-init. The code is self-documenting, easy to modify, and requires no build step to test.
The Hidden Complexity of "Simple" React
State Management Overhead
For a small project, you might think you can just use useState and be done. And you can—until you need to share state between two sibling components. Then you have to lift state up to a parent, or use a context provider, or reach for a state management library. Suddenly, your "simple" app has a state tree.
Alpine.js avoids this entirely. Since directives are scoped to their DOM element, and you can nest x-data scopes, state is naturally localized. If two elements need to share state, you simply place them in the same x-data scope. No prop drilling, no context, no ceremony.
The Build Step Trap
I have lost count of how many times I have seen a small React project abandoned because the developer spent more time configuring the build than writing features. Webpack configs, Babel plugins, CSS module setup—these are not trivial. Even with Vite, which is fast, you still need to run npm run dev and wait for the dev server. Alpine.js has no dev server. You edit the HTML file, refresh the browser, and see your changes. For rapid prototyping, this is unmatched.
When You Should Stick with React
I am not suggesting you abandon React entirely. There are clear cases where React is the better choice. If you are building a large-scale application with dozens of routes, complex state interactions, and a team of multiple developers, React's ecosystem and patterns will save you time. The component model, virtual DOM, and tooling become assets at scale, not liabilities.
Alpine.js also has limitations. It is not designed for heavy client-side routing. For a single-page application with many views, you would need to pair it with something like HTMX or a lightweight router, and you might find yourself recreating patterns that React gives you for free. If your application needs to manage deeply nested state or complex animations, React's useReducer and libraries like Framer Motion are more mature.
A Concrete Example: The Booking Widget
Let me share a specific project that sealed my decision. A friend runs a small salon in Osijek. She needed a booking widget on her website: a date picker, a time slot selector, and a confirmation modal. The backend was a simple PHP script that returned available slots. The frontend needed to be fast, lightweight, and easy to maintain.
I built it with Alpine.js in about three hours. The entire widget was a single HTML file with inline styles. The date picker used x-model to track the selected date, x-init to fetch available slots from the backend, and x-show to display the confirmation modal. The code was 150 lines total. My friend's webmaster, who knows basic HTML and CSS, was able to modify the widget later without calling me.
If I had built this with React, I would have had to set up a project, explain JSX to the webmaster, and deal with the deployment pipeline. The React version would have taken longer to build and longer to maintain. The Alpine.js version was delivered faster, performed better, and was more maintainable by someone without a JavaScript framework background.
The Practical Takeaway
Next time you start a small project—a landing page, an internal tool, a simple interactive widget—ask yourself one question: "Does the complexity of a full JavaScript framework genuinely serve this project, or am I just following a habit?" If the answer leans toward habit, try Alpine.js. You will be surprised how much you can build with a single script tag, a few x- directives, and no build step. Your future self, and your clients, will thank you for the simplicity.
The web is moving toward lighter, more resilient patterns. Tools like Alpine.js, HTMX, and vanilla JavaScript are making a strong comeback. They are not regressions—they are refinements. For many projects, the best framework is the one you can stop thinking about.