In this tutorial we’ll build a Laravel blog using Livewire for seriously snappy front-end responsiveness.
Livewire is a framework for Laravel which takes the pain out of creating highly responsive & dynamic front-end UIs. Usually, you’d need to install a reactive framework like React, Vue or AlpineJS to get highly interactive front-end functionality. But with Livewire, all of that is handled for you without needing to step out from the familiar Laravel codebase.
Livewire was created by Caleb Porzio and has since gained significant traction in the Laravel community.
It lets developers write front-end components using the same syntax and techniques that they’re used to for backend development.
Especially cool is the ability to reflect the server’s state on the front-end without a full page reload. Nice! This lets us create websites that respond nearly instantly to user interaction without having to refresh the entire page.
We’ll make use of this to introduce two responsive features to our blog:
Let’s get started – we’re going to build a very basic blog application that’ll show an index page of articles as well as a single article view.
Our simple blog application will feature:
wire:navigate
directiveIntroduction to Livewire
Livewire is fairly easy to set up. You’ll need to make sure you’ve got a suitable local environment to run this tutorial on your own machine.
php artisan serve
to run a local dev serverLet’s get going
If you haven’t already got a Laravel project set up, navigate to where you want the project to live and run
composer create-project --prefer-dist laravel/laravel your-project-name
Composer will go away and do its thing for a while, you’ll see a bunch of stuff getting installed.
If you get any errors at this stage, you might need to ensure you’ve got the right PHP version and all appropriate extensions are installed – you may also need to install pdo_sqlite with sudo apt install php8.2-sqlite3
(swap out the appropriate PHP version)
Then navigate into your newly created project folder
cd your-project-name
With your Laravel blog project now installed, it’s time to get Livewire set up and configured
composer require livewire/livewire
We’ll also need to publish Livewire’s configuration settings
php artisan livewire:publish
This will copy Livewire’s default config settings into config/livewire.php
– this isn’t vital, but it’s a nice-to-have if you want an easy way to review and update any of Livewire’s core configuration values.
The easiest way to get a database up and running for this simple blog app will be to make use of sqlite
. This is a super-easy file-based database that’s great for local development (you can use it in production too if you really want, though I wouldn’t recommend it!)
First, make sure sqlite
is installed
sudo apt-get install sqlite3
Then create a new database file. From the root of your Laravel project folder, run
touch database/database.sqlite
You’ll also need to update your project’s .env
file to use this new database
DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/your_project_name/database/database.sqlite
If all has gone to plan, you can now run
php artisan serve
and your fresh new Laravel installation will become available to view in your browser at https://127.0.0.1:8000
(you should be able to confirm the precise access URL after artisan serve
has finished processing)
For this example, let’s set up an Article model and controller, and populate the database with some example articles that we’ll use when building out our Livewire views.
Each article will represent a Laravel blog page, with a title, blog content and a featured image (we’ll use LoremFlickr to populate dummy images)
That’s all very basic for now, but Laravel will make it a breeze to expand upon all this later if you want to carry on building on the basics outlined here.
First, let’s create a new migration with the artisan
command
php artisan make:migration create_articles_table
This’ll create a new database migration file under database/migrations
. Modify the up()
method so it looks like this:
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->id(); // Auto-incremental primary key
$table->string('title'); // Title of the article
$table->text('content'); // Content of the article
$table->string('image')->nullable(); // Adding the image column
$table->timestamps(); // created_at and updated_at timestamps
});
}
Then run
php artisan migrate
This will run the migration, creating the articles
table in the database and setting the stage for us to start populating example blog data.
Let’s get some test data in place too, so we can see what’s going on more easily. We can set up a Laravel blog seeder to populate our articles table
php artisan make:seeder ArticlesTableSeeder
This’ll make a new file under database\seeders
. Edit the run()
method so it looks like:
public function run(): void
{
$faker = Faker::create();
foreach (range(1, 10) as $index) {
DB::table('articles')->insert([
'title' => $faker->sentence(6, true),
'content' => $faker->paragraphs(3, true),
'image' => "https://placekitten.com/500/500", //meow
'created_at' => now(),
'updated_at' => now(),
]);
}
}
You also need to make sure that the Faker
library is installed via composer
composer require fzaninotto/faker
And include the Faker
and DB
facades by adding these lines after the namespace
declaration
...
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Faker\Factory as Faker;
class ArticlesTableSeeder extends Seeder
{
...
Now you’re all set up and ready to generate the dummy data. Run this command
php artisan db:seed --class=ArticlesTableSeeder
And if you need to reset the database and start fresh with the same test data, you can run
php artisan migrate:fresh --seed --seeder=ArticlesTableSeeder
Let’s start setting up all the files we’ll need to create our new blog in Laravel.
Now we’ve got a database with a few example articles in it, we’re ready to start building the front-end that’ll serve these blog posts to our site’s visitors.
For this tutorial, I’m using Tailwind to style the page. I’ll be including the Tailwind CDN CSS file directly, so no need to install it with Laravel.
To generate the controller and views for our little Laravel blog, run the following command from project root
php artisan make:model Article --controller
This tells Artisan that we’re looking to do two things:
First though, let’s handle the controller
Take a look at the newly generated file, app\Http\Controllers\ArticleController.php
. You should see an empty class, ArticleController
.
We’ll need to add an index()
method. Add the following methods to the controller
public function index()
{
$articles = Article::all();
return view('articles.index', compact('articles'));
}
You’ll also need to inform the controller that it should use the Laravel Model we’ll be generating shortly. Add the following at the top of the file, after the namespace
line
...
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
class ArticleController extends Controller
{
...
Now we’ll also need to make the relevant views. Thankfully, Taylor Otwell added the make:view
command to Artisan recently to make life easier for us developers!
You’ll need to make sure you’re running at least Laravel 10.23.0 to make use of this.
Now, run the following commands seperately to generate the views we’ll use (the --pest
argument also creates a handy Feature Test within the /tests
directory. We’re not touching that for now, but it’ll be useful in the future!)
We’ll only work with the index
and show
views here. Later on, we can introduce edit
and create
views to allow for post editing.
php artisan make:view articles.index --pest
php artisan make:view articles.show --pest
We’ll start by editing the index.blade.php
file, newly generated under /resoures/views/articles
. Edit the contents of this file so it looks a little like this
@extends('layouts.app')
@section('content')
<div class="container mx-auto">
<h2 class='my-4 text-2xl'>Article Index</h2>
<div class="grid grid-cols-3 gap-4">
@foreach($articles as $article)
<div class="bg-white p-4 rounded shadow">
<div class="mb-4">
<h3 class="text-xl font-bold mb-4">{{ $article->title }}</h3>
<p>{{ Str::limit($article->content, 100) }}</p>
</div>
<img src="{{ $article->image }}" alt="{{ $article->title }}" class="w-full h-48 object-cover mb-2">
<div class="pt-4 font-bold">
<a href="/articles/{{ $article->id }}">Read More</a>
</div>
</div>
@endforeach
</div>
</div>
@endsection
Next, we’ll need to create a layout file which will act as the global container for our app. For our simple blog application, this will be a basic template with some generic styling and a small nav menu.
For simplicity, I’ve also linked to the Tailwind CSS CDN-hosted stylesheet. In the final project, we’d be building our own Tailwind CSS locally.
Create the file resources/views/layouts/app.blade.php
and place this code inside it
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laravel Blog Project</title>
<!-- Link to Tailwind CSS via CDN for simplicity -->
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<!-- If you've set up Tailwind locally, you might link to your compiled CSS file instead -->
<!-- <link href="{{ asset('css/app.css') }}" rel="stylesheet"> -->
</head>
<body class="bg-gray-100 text-gray-800">
<nav class="bg-blue-600 text-white p-4">
<div class="container mx-auto">
<h1 class="text-2xl font-bold">Laravel Blog Project</h1>
</div>
</nav>
<div class="container mx-auto mt-4 p-4">
@yield('content')
</div>
<footer class="bg-blue-600 text-white p-4 mt-8">
<div class="container mx-auto text-center">
© {{ date('Y') }} Laravel Blog Project. All rights reserved.
</div>
</footer>
</body>
</html>
Our Article
model is very straightforward, and we won’t need to change the file that Artisan has created for us.
All we’re using it for is to create a representation in memory of our Laravel blog database records.
If you want to take a look, the model definition code in app\Models\Article.php
looks like this
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use HasFactory;
}
If you continue after this tutorial to expand on the blog functionality and add, for example, editorial control, you’d need to modify the model code to include a $fillable
property to let Laravel know that mass assignment will be taking place to the database table.
That’s a security measure in place to guard against known vulnerabities with user generated content.
Aside from that, you’d also need to deal with relationships between models, validation, security.
There’s a lot more to cover when building something like a full Laravel blog app, but we’re going to keep it simple for now!
Now, we’ve got a view file for our articles index page, but how do we direct visitors there? Well, we make use of Laravel’s super-handy Routing functionality.
Add the following code to /routes/web.php
use App\Http\Controllers\ArticleController;
Route::resource('articles', ArticleController::class);
This’ll tell Laravel that whenever a visitor hits http://127.0.0.1:8000/articles
they should see the generated view that we’ve just created. Give it a try!
Your development server should now be showing something like this.
What you’re seeing here is the Laravel Blade view that’s been generated via your web route, Article Controller and Model.
Currently, it’s not responsive to user input, but let’s start working some Livewire magic and get our front-end up to speed!
To make sure that our layout knows about Livewire and can react to our input events, let’s make a few small changes.
First, edit resources/views/layouts/app.blade.php
and add the following:
@livewireStyles
just before the closing </head>
tag@livewireScripts
just before the closing </body>
tag ....
@livewireStyles
</head>
<body class="bg-gray-100 text-gray-800">
....
@livewireScripts
</body>
</html>
Next, run the command
php artisan make:livewire ArticlesIndex
This will create our first Livewire component, which we’ll be using to show the list of articles.
This will generate two new files:
app/Livewire/ArticlesIndex.php
(the component’s class)resources/views/livewire/articles-index.blade.php
(the component’s view)Let’s make use of pagination functionality so we can easily navigate through the list of blog posts.
This will be fairly standard stuff – “next” and “previous” buttons and a list of pages. We’ll set up our article view to show 6 blog posts on each page.
In the ArticlesIndex.php
file, you can make use Laravel’s built-in pagination to simplify this task, update the file like so
namespace App\Livewire;
use Livewire\Component;
use App\Models\Article;
use Livewire\WithPagination;
class ArticlesIndex extends Component
{
use WithPagination;
public function render()
{
return view('livewire.articles-index', [
'articles' => Article::latest()->paginate(6)
]);
}
}
Then in resources/views/livewire/articles-index.blade.php
, display the articles and add pagination
<div>
<div class="grid grid-cols-3 gap-4">
@foreach($articles as $article)
<div class="bg-white p-4 rounded shadow">
<div class="mb-4">
<h3 class="text-xl font-bold mb-4">{{ $article->title }}</h3>
<p>{{ Str::limit($article->content, 100) }}</p>
</div>
<img src="{{ $article->image }}" alt="{{ $article->title }}" class="w-full h-48 object-cover mb-2">
<div class="pt-4 font-bold">
<a wire:navigate href="/articles/{{ $article->id }}">Read More</a>
</div>
</div>
@endforeach
</div>
<div class="mt-4">
{{ $articles->links() }}
</div>
</div>
We’re now going to modify our existing Article index view template, resources/views/articles/index.blade.php
. Remove everything from this file and replace with the much simpler layout
@extends('layouts.app')
@section('content')
<h2 class='my-4 text-2xl'>Article Index</h2>
@livewire('articles-index')
@endsection
This should be everything needed to power our new Livewire-enabled blog layout. Take another look at your development server.
It should be looking a little like this.
Fairly similar, but now see the new pagination options in the bottom right?
You should be able to click on them and see the blog posts seamlessly flip between pages 1 and 2. This is all taking place on the client side with requests to the server happening in the background.
If you inspect the network activity while you’re navigating the page, you’ll see these invisible page requests.
And all of this has happened without you touching a single line of a front-end reactive framework!
Now that we’ve sorted out our blog index page, it should be straightforward enough to get the “Read More” link working to show us the full blog article.
Let’s follow the same process to create a Livewire component to show us a single blog page
php artisan make:livewire ArticleShow
This will create another two files for us
app/Livewire/ArticleShow.php
(the component’s class)resources/views/livewire/article-show.blade.php
(the component’s view)We only need to make a few changes to ArticleShow.php
(Note that Livewire should automatically assign the article data to public $article
but the mount()
method can be used to ensure this happens)
You can think of mount()
in the same way as the standard PHP __construct()
class constructor method.
namespace App\Livewire;
use Livewire\Component;
use App\Models\Article;
class ArticleShow extends Component
{
public $article;
public function mount(Article $article)
{
$this->article = $article;
}
public function render()
{
return view('livewire.article-show');
}
}
This controller simply renders the appropriate view file, article-show.blade.php
And inside article-show.blade.php
, add the following code
<div>
<h2 class="text-2xl mb-4">{{ $article->title }}</h2>
<p>{{ $article->content }}</p>
<div class='mt-8 font-bold'>
<a wire:navigate href="/articles">Back to articles</a>
</div>
</div>
Note the additional link at the end which we’ll use to navigate back to the article list
We also need to route the front-end requests to this new Livewire component. Update the routes\web.php
file so it contains this
...
Route::get('/', function () {
return view('welcome');
});
use App\Livewire\ArticlesIndex;
use App\Livewire\ArticleShow;
Route::get('/articles', ArticlesIndex::class);
Route::get('/articles/{article}', ArticleShow::class);
And there’s one more file to create now. We still need to create a layout file for full page components to render into. Add a new file, resources/views/components/layouts/app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laravel Blog Project</title>
<!-- Link to Tailwind CSS via CDN for simplicity -->
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<!-- If you've set up Tailwind locally, you might link to your compiled CSS file instead -->
<!-- <link href="{{ asset('css/app.css') }}" rel="stylesheet"> -->
@livewireStyles
</head>
<body class="bg-gray-100 text-gray-800">
<nav class="bg-blue-600 text-white p-4">
<div class="container mx-auto">
<h1 class="text-2xl font-bold">Laravel Blog Project</h1>
</div>
</nav>
<div class="container mx-auto mt-4 p-4">
{{ $slot }}
</div>
<footer class="bg-blue-600 text-white p-4 mt-8">
<div class="container mx-auto text-center">
© {{ date('Y') }} Laravel Blog Project. All rights reserved.
</div>
</footer>
@livewireScripts
</body>
</html>
This is very similar to our original layouts/app.blade.php
layout except we’re now using {{ $slot }}
instead of the Laravel yield
directive since that’s the method Laravel uses for embedding content into components.
Go back to your development server once more and you should see the following after clicking on a “Read More” link
Perhaps the least inspiring blog design you’ve ever seen! (there’s a lot of room for improvement 😂)
So what’s going on here?
We’re making use of a component rendering technique known as “full page components” within Livewire to build out our Laravel blog.
We’re essentially rendering two completely seperate components via the two routes /articles
and (e.g.) /articles/3
– the wire:navigate
Whenever we load up /articles
or /articles/3
in our browser, Laravel makes a server-side call to render this layout with the appropriate components in place. (this is vital from an SEO point of view too, as it means search engines will also have a rapidly generated HTML page to process)
But after this point, whenever a user clicks on a link to perform an action, an AJAX call is sent out to fetch new HTML – this is either
So you can see how by using Livewire’s components, we can perform some rather clever tricks after the intial page load is complete.
The way that Livewire turns a server-side rendered app into a website that can use client-side routing to create a “single page app” experience isn’t that complicated:
One of the coolest features of Livewire is its ability to cache computed properties. This is perfect for pulling in articles or any other data that doesn’t change often. This is especially useful for a Laravel blog where we’re not expecting the content of an article to update often.
Let’s say you have an Article
model. Here’s how you can cache its properties:
use Livewire\Component;
class ShowArticles extends Component
{
public function getArticlesProperty()
{
return cache()->remember('articles', 3600, function () {
return Article::all();
});
}
public function render()
{
return view('livewire.show-articles', ['articles' => $this->articles]);
}
}
In the example above, Livewire will cache the articles for an hour (3600 seconds). This means super-fast data retrieval!
Try implementing that in our Article post type above
You’ll have noticed the use of wire:navigate
in the Laravel blog code. This is a really handy feature in Livewire and enables transitions between pages to happen without a page reload.
As above, we can consider situations where we’d want to reload only sections of a page, but out-of-the-box Livewire enables websites to skip page reloads entirely, allowing seamless navigation between all sections of a site.
For our example, we might want three pages: main, about, and contact.
// routes/web.php
Route::get('/', MainComponent::class);
Route::get('/about', AboutComponent::class);
Route::get('/contact', ContactComponent::class);
Now, in your blade view, you can use wire:navigate
for navigation:
<a wire:navigate href="/about">About Us</a>
<a wire:navigate href="/contact">Contact Us</a>
This ensures that when a user clicks on these links, the page loads are lightning-fast!
If you feel like expanding the blog app out into a full website, you can use wire:navigate
to let the user jump between pages with ease.
Robin is the dedicated developer behind Solarise.dev. With years of experience in web development, he's committed to delivering high-quality solutions and ensuring websites run smoothly. Always eager to learn and adapt to the ever-changing digital landscape, Robin believes in the power of collaboration and sharing knowledge. Outside of coding, he enjoys diving into tech news and exploring new tools in the industry.
If you'd like to get in touch to discuss a project, or if you'd just like to ask a question, fill in the form below.
Send me a message and I'll get back to you as soon as possible. Ask me about anything - web development, project proposals or just say hi!