LessonsBuild a Blog → Part 7

Frontend — Public Blog

Laravel
⏱ 25 min read🏗️ ProjectNot completed

Now 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.
← Previous Next Lesson →
💡

Stuck? Need help?

Review the previous lessons or check the Laravel documentation.

Laravel Docs →