Rebuilding my site with Elide for Laravel and HTMX
Intro - Rebuilding my personal site
I've had a personal site for years, but rarely used it. Motivated by attending Laracon AU in 2023 and 2024, I rebuilt it a couple times - each version an excuse to play with new ideas.
Now that we're in the back half of 2025, I've rebuilt it again. This time, with a purpose: to start writing more regularly, and to dogfood the things I work on.
This rebuild (which you're reading this post on) kicks that off - it's built using a package I recently released: Elide for Laravel + HTMX.
What is HTMX
From their own site:
htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext
What is Elide
Short, sharp, shiny - Elide is a package which streamlines the gap between a Laravel backend and a HTMX frontend.
Elide:
- ...is strongly inspired by Inertia's implementation - it brings some of the "Inertia experience" to HTMX projects, while embracing HTMX's frontend simplicity and strengths
- ...strives to make "the network gap" and HTMX's out of band swaps as invisible as possible
- ...doesn't want to change (too much) how you use core Laravel features
Why I built it
Most of my dev work these days seems to be with Intertia+React - it's a very productive stack, and perhaps more importantly, it's a fun stack to work with.
I've also spent a bunch of time with HTMX in the last 6+ months. HTMX brings dynamic interactivity with very little effort, and does not require a JS build step.
I did find myself missing the "Inertia experience" however. As a project grew, HTMX routes were accumulating boilerplate and logic for handling full or partial responses. None of that was difficult, but it became fiddly - that adds up.
How Elide works (very briefly)
For most cases Elide is quite simple to use.
Similar to Inertia it requires a core "application view" which is used to render a full page response.
For any component/island in your site's frontend - e.g.: site navigation, site footer, content area, user profile menu, etc - you would have a Blade Component to render that.
You then configure your routes (and/or app's service provider) to return only the components which require refreshing to the frontend. Elide wraps components in Partial
objects - these are "smart markers" that indicate which parts of the page should be dynamically swappable via HTMX. In views these are marked using the @htmxPartial(...)
directive.
Hypothetical scenario - you have a route which displays a blog post in the browser.
With Inertia it might look something like this:
use Inertia\Inertia;
use App\Models\Post;
Route::get('/blog/{post:slug}', function(Post $post) {
return Inertia::render('blog/post', [
'post' => $post,
]);
});
With Elide it might look like this:
use Elide\Htmx;
use App\Models\Post;
use App\View\Components\PostContent;
Route::get('/blog/{post:slug}', function(Post $post) {
return Htmx::render(new PostContent($post));
});
Quite similar!
If someone lands on a fresh browser tab for that route Elide will return the full application view, including the rendered PostContent
component.
If someone has triggered an HTMX enhanced AJAX navigation, Elide will detect that and only return the rendered PostContent
partial. HTMX will automatically swap that partial into the right place for us.
Progressive enhancement is where HTMX shines, and Elide makes the backend side of these interactions more streamlined.
You can read more about how to use Elide over at Github: https://github.com/danherbert-io/elide-for-laravel
Building the site with Elide
The stack for this new build is:
- Laravel (no starter kit)
- Elide (with HTMX via CDN link in the frontend)
- Tailwind
Elide and HTMX made it very quick to build this simple site out, and make it dynamic in the process too.
The core application view: app.blade.php
This site is simple, it didn't need too much:
- A place to show dynamic navigation -
@htmxPartial('site-navigation')
- A place to show dynamic content -
@htmxPartial('content
)- NB: When using
Htmx::render(...)
, the component provided gets put into the "maincontent
partial"
- NB: When using
A (very heavily stripped) version might look like this:
<html>
<head>
<title>{{$title ?? config('app.name')}}</title>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js" defer
integrity="sha384-Akqfrbj/HpNVo8k11SXBb6TlBWmXXlYQrCSqEWmyKJe+hDm3Z/B2WVG4smwBkRVm"
crossorigin="anonymous"></script>
@vite(['resources/css/app.css'])
</head>
<body>
<header> @htmxPartial('site-navigation') </header>
<main> @htmxPartial('content') </main>
</body>
</html>
Creating pages
In a small site such as this, my content and posts are markdown files. To support that, I needed a page component which could display that markdown.
php artisan make:component Page\\MarkdownPage
class MarkdownPage extends Illuminate\View\Component
{
public readonly string $title;
public readonly string $content;
public function __construct(string $markdownFile)
{
$page = markdown($markdownFile);
$this->content = $page['content'];
$this->title = $page['title'];
}
public function render()
{
return view('components.page.markdown-page')
}
}
A simplified version of the template is something like:
<div class="prose max-w-none">
{!! $content !!}
</div>
The route is also straightforward:
use Elide\Htmx;
use View\Components\Page\MarkdownPage
Route::get('/the-story', function() {
$page = new MarkdownPage('content/the-story.md');
return Htmx::render($page)->title($page->title);
});
Dynamic navigation
Setting up dynamic navigation was also a smooth process.
First I created the component:
php artisan make:component Ui\\SiteNavigation
The template for this component:
($routes
is declared in the component)
<nav hx-boost="true">
<ul>
@foreach ($routes as $route => $label)
<li>
<a href="{{route($route)}}"
@if(Route::is($route)) class="font-bold" @endif
>
{{$label}}
</a>
</li>
@endforeach
</ul>
</nav>
Tip
An important thing to note here is the hx-boost="true"
attribute. This instructs HTMX to make every click on the child <a>
elements an HTMX AJAX request.
In the core application view (see above), we're using @htmxPartial('site-navigation')
to include the site navigation component.
@htmxPartial('site-navigation')
Because I'm referring to a partial and not the SiteNavigation
component itself, we need to instruct Elide to connect the dots. Additionally, because this is site navigation, we probably want this to be updated with all pages so that the current active link is visible to users as they navigate through the site.
This could be done in each route, but as we want it included with all routes we can do this more simply by updating our AppServiceProvider
:
use Illuminate\Support\ServiceProvider;
use App\View\Components\Ui\SiteNavigation;
use Elide\Enums\RequestKind;
use Elide\Htmx;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Htmx::usingPartials(
fn () => [SiteNavigation::class],
for: RequestKind::BOTH,
);
}
}
This is fine in a very simple site like this. With a more complicated site I would probably use a route group + middleware to define which components need to be sent.
Presto!
Without writing a single line of JavaScript, or including decision logic in the routes, I've got a site which returns full page responses or only site navigation and content to swap into the frontend.
DX takeaways
Elide is very fresh and will be refined as time goes on, but I'm already really happy with how much it streamlined the build out of this new site with HTMX.
I was able to get into a rhythm very quickly, and the end result has a very clear set of components and responsibilities.
Things I enjoyed:
- Much less boilerplate, and no route decisions about what to return when AJAX/non-AJAX
- A stronger focus on the UI components themselves
- No JS written at all (don't get me wrong, I really like JS!)
- A really quick site build
- A lean frontend
- Less moving parts means better testability and improved maintainability
Conclusion
If you happen to be reading this and you're into HTMX, give Elide a try. Hopefully it streamlines your workflow. I'd love to hear how it helped, or ideas on how it can be improved.
- Elide at Github - docs, recipes, and a simple demo site showing dialogs+toast notifications+user profile
Like and subscribe.But seriously - if you try Elide I'd love to hear about it. Feedback and suggestions are always welcome.