Frontend — Public Blog
LaravelNow let's build the public-facing blog — the homepage with posts list, individual post pages, and category pages.
Blog Controller
app/Http/Controllers/BlogController.php
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category;
use App\Models\Tag;
class BlogController extends Controller
{
public function index()
{
$posts = Post::published()
->with(['user', 'category', 'tags'])
->withCount('approvedComments')
->latest('published_at')
->paginate(9);
$categories = Category::withCount('publishedPosts')
->having('published_posts_count', '>', 0)
->get();
$popularPosts = Post::published()
->popular()
->take(5)
->get();
return view('blog.index', compact('posts', 'categories', 'popularPosts'));
}
public function show(Post $post)
{
// Only show published posts
abort_if(!$post->published, 404);
// Increment views
$post->increment('views');
$post->load(['user', 'category', 'tags', 'approvedComments.user']);
// Related posts (same category)
$relatedPosts = Post::published()
->where('category_id', $post->category_id)
->where('id', '!=', $post->id)
->take(3)
->get();
return view('blog.show', compact('post', 'relatedPosts'));
}
public function category(Category $category)
{
$posts = Post::published()
->where('category_id', $category->id)
->with(['user', 'category'])
->latest('published_at')
->paginate(9);
return view('blog.category', compact('category', 'posts'));
}
public function tag(Tag $tag)
{
$posts = Post::published()
->whereHas('tags', fn($q) => $q->where('tags.id', $tag->id))
->with(['user', 'category'])
->latest()
->paginate(9);
return view('blog.tag', compact('tag', 'posts'));
}
}Public Routes
routes/web.php
<?php
use App\Http\Controllers\BlogController;
Route::get('/', [BlogController::class, 'index'])->name('blog.index');
Route::get('/post/{post:slug}', [BlogController::class, 'show'])->name('blog.show');
Route::get('/category/{category:slug}', [BlogController::class, 'category'])->name('blog.category');
Route::get('/tag/{tag:slug}', [BlogController::class, 'tag'])->name('blog.tag');Blog Homepage View
resources/views/blog/index.blade.php
@extends('layouts.app')
@section('title', 'Laravel Blog')
@section('content')
<div class="max-w-6xl mx-auto px-4 py-8">
<div class="grid grid-cols-3 gap-8">
<!-- Posts Grid (2/3 width) -->
<div class="col-span-2">
<div class="grid grid-cols-2 gap-6">
@forelse($posts as $post)
<article class="bg-white rounded-lg shadow overflow-hidden hover:shadow-md transition">
@if($post->image)
<img src="{{ Storage::url($post->image) }}"
alt="{{ $post->title }}"
class="w-full h-48 object-cover" />
@endif
<div class="p-5">
@if($post->category)
<a href="{{ route('blog.category', $post->category) }}"
class="text-xs font-semibold uppercase tracking-wide"
style="color: {{ $post->category->color }}">
{{ $post->category->name }}
</a>
@endif
<h2 class="text-lg font-bold mt-2 mb-2">
<a href="{{ route('blog.show', $post) }}"
class="hover:text-blue-600">
{{ $post->title }}
</a>
</h2>
<p class="text-gray-500 text-sm">{{ $post->excerpt }}</p>
<div class="flex items-center justify-between mt-4 text-xs text-gray-400">
<span>{{ $post->user->name }}</span>
<span>{{ $post->published_at->diffForHumans() }}</span>
</div>
</div>
</article>
@empty
<div class="col-span-2 text-center text-gray-400 py-12">No posts yet.</div>
@endforelse
</div>
<div class="mt-8">{{ $posts->links() }}</div>
</div>
<!-- Sidebar (1/3 width) -->
<aside>
<div class="bg-white rounded-lg shadow p-5 mb-6">
<h3 class="font-bold mb-3">Categories</h3>
@foreach($categories as $category)
<a href="{{ route('blog.category', $category) }}"
class="flex justify-between py-2 border-b text-sm hover:text-blue-600">
<span>{{ $category->name }}</span>
<span class="text-gray-400">{{ $category->published_posts_count }}</span>
</a>
@endforeach
</div>
<div class="bg-white rounded-lg shadow p-5">
<h3 class="font-bold mb-3">Popular Posts</h3>
@foreach($popularPosts as $popular)
<a href="{{ route('blog.show', $popular) }}"
class="block py-2 border-b text-sm hover:text-blue-600">
{{ $popular->title }}
<span class="text-gray-400 text-xs block">{{ number_format($popular->views) }} views</span>
</a>
@endforeach
</div>
</aside>
</div>
</div>
@endsection💡
Use route model binding with slugs!
Route::get('/post/{post:slug}') automatically finds the post by slug instead of ID. Much cleaner URLs and better for SEO.Stuck? Need help?
Review the previous lessons or check the Laravel documentation.