Nội dung bài viết
- 1.Tạo view và trả view từ route, controller
- 2.Cú pháp Blade cơ bản
- 2.1In dữ liệu: {{ }} và {!! !!}
- 2.2Điều kiện và vòng lặp
- 2.3Chèn view con với @include
- 3.Layout bằng component (cách khuyến nghị)
- 4.Layout bằng kế thừa: @extends, @section, @yield
- 5.Component Blade: tái sử dụng giao diện
- 6.View với form: @csrf, @method, @error
- 7.Lỗi thường gặp với view
- 8.Kinh nghiệm từ Hozitech
View trong Laravel là tầng giao diện, chứa HTML mà người dùng nhìn thấy, tách khỏi logic xử lý trong controller. View được viết bằng Blade, template engine của Laravel, lưu thành file .blade.php trong thư mục resources/views và được trả về bằng helper view('ten-view', $duLieu). View là chữ V trong mô hình MVC.
Bài viết cập nhật theo Laravel 13 (bản hiện hành, PHP 8.3+): tạo view, truyền dữ liệu, cú pháp Blade cơ bản, layout bằng component và bằng kế thừa, component <x-...> và các lỗi thường gặp.

Tạo view và trả view từ route, controller
php artisan make:view posts.index
Lệnh tạo file resources/views/posts/index.blade.php. Dấu chấm trong tên view tương ứng với thư mục con. Trả view từ route hoặc controller:
use App\Models\Post;
use Illuminate\Support\Facades\Route;
Route::get('/posts', function () {
return view('posts.index', [
'posts' => Post::latest()->paginate(10),
]);
});
// hoặc thêm từng biến
return view('posts.show')->with('post', $post);
Trong thực tế, phần lấy dữ liệu nên nằm trong controller, route chỉ trỏ tới controller; xem bài controller trong Laravel. Biến cần có ở mọi view (ví dụ thông tin liên hệ ở footer) có thể chia sẻ bằng View::share('key', $value) trong phương thức boot() của AppServiceProvider.
Cú pháp Blade cơ bản
In dữ liệu: {{ }} và {!! !!}
<h1>{{ $post->title }}</h1> {{-- tự escape HTML, an toàn --}}
<div class="noi-dung">
{!! $post->body_html !!} {{-- in HTML thô, KHÔNG escape --}}
</div>
{{ }} chuyển ký tự đặc biệt thành thực thể HTML, chặn được việc chèn script (XSS). Chỉ dùng {!! !!} với nội dung bạn tin cậy hoặc đã được lọc, ví dụ HTML từ trình soạn thảo trong trang quản trị sau khi làm sạch.
Điều kiện và vòng lặp
@if là câu lệnh điều kiện, còn @foreach là vòng lặp. @forelse tiện hơn khi danh sách có thể rỗng:
@if ($posts->isEmpty())
<p>Chưa có bài viết.</p>
@endif
@forelse ($posts as $post)
<article @class(['bai-viet', 'bai-dau' => $loop->first])>
<span>{{ $loop->iteration }}.</span>
<a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a>
</article>
@empty
<p>Chưa có bài viết.</p>
@endforelse
{{ $posts->links() }}
Biến $loop có sẵn trong vòng lặp: $loop->first, $loop->last, $loop->iteration (đếm từ 1). Dòng $posts->links() in thanh phân trang, chi tiết trong bài phân trang trong Laravel.
Chèn view con với @include
@include('partials.menu')
@include('partials.post-card', ['post' => $post])
Layout bằng component (cách khuyến nghị)
Hầu hết các trang dùng chung khung HTML (head, header, footer). Cách hiện đại là định nghĩa layout như một component trong resources/views/components:
<!-- resources/views/components/layout.blade.php -->
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="utf-8">
<title>{{ $title ?? 'Blog Laravel' }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
@include('partials.menu')
<main>
{{ $slot }}
</main>
</body>
</html>
<!-- resources/views/posts/index.blade.php -->
<x-layout>
<x-slot:title>Danh sách bài viết</x-slot>
@foreach ($posts as $post)
<h2>{{ $post->title }}</h2>
@endforeach
</x-layout>
Nội dung đặt giữa <x-layout> đi vào biến $slot; slot có tên như <x-slot:title> đi vào biến $title.
Layout bằng kế thừa: @extends, @section, @yield
Đây là cách truyền thống trước khi có component, vẫn được hỗ trợ và gặp nhiều trong dự án cũ:
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
<title>@yield('title', 'Blog Laravel')</title>
</head>
<body>
<div class="container">
@yield('content')
</div>
</body>
</html>
<!-- resources/views/posts/show.blade.php -->
@extends('layouts.app')
@section('title', $post->title)
@section('content')
<h1>{{ $post->title }}</h1>
{!! $post->body_html !!}
@endsection
Không nên trộn hai kiểu layout trong cùng một nhóm trang; chọn một kiểu để người bảo trì dễ theo.
Component Blade: tái sử dụng giao diện
Component ẩn danh (anonymous component) chỉ cần một file Blade, không cần class. Cú pháp <x-alert> thay cho @component('components.alert') trong các bài hướng dẫn cũ:
<!-- resources/views/components/alert.blade.php -->
@props(['type' => 'info'])
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $slot }}
</div>
<x-alert type="success" class="mb-4">
Đã lưu bài viết.
</x-alert>
<x-alert type="error" :message="$message" />
@propskhai báo biến nhận vào và giá trị mặc định.- Thuộc tính không nằm trong
@props(nhưclass="mb-4") vào$attributesvà được gộp bằngmerge(). - Dấu hai chấm trước thuộc tính (
:message="$message") truyền biểu thức PHP thay vì chuỗi. - Component cần logic riêng thì tạo kèm class:
php artisan make:component Alert; chỉ cần file view thì thêm--view.
View với form: @csrf, @method, @error
<form method="POST" action="{{ route('posts.update', $post) }}">
@csrf
@method('PUT')
<input name="title" value="{{ old('title', $post->title) }}">
@error('title')
<p class="loi">{{ $message }}</p>
@enderror
<button type="submit">Cập nhật</button>
</form>
@csrf thêm token chống giả mạo request (xem bài CSRF trong Laravel), @error in lỗi validation của từng trường, old() giữ lại giá trị người dùng đã nhập khi form bị trả về.
Lỗi thường gặp với view
- View [posts.index] not found: sai tên thư mục hoặc dùng dấu chấm trong tên thư mục.
- Undefined variable $posts: quên truyền biến khi gọi
view(), hoặc trùng tên biến giữa view cha và view con. - Đặt truy vấn trong view như
Post::all()ngay trong Blade: khó kiểm soát và dễ sinh N+1 query. Lấy dữ liệu ở controller, nạp quan hệ bằngwith()như trong bài model trong Laravel. - Sửa view không thấy thay đổi trên server: chạy
php artisan view:clear; khi triển khai, chạyphp artisan view:cacheđể biên dịch sẵn.
Kinh nghiệm từ Hozitech
Ở các website Laravel do Hozitech phát triển, giao diện được chia thành layout chung và các component nhỏ (thẻ bài viết, nút, khối kêu gọi liên hệ) để một thay đổi thiết kế chỉ cần sửa một file. Nội dung HTML do người quản trị nhập qua trình soạn thảo được làm sạch trước khi in bằng {!! !!}, còn mọi dữ liệu khác in bằng {{ }}.
Nếu doanh nghiệp cần giao diện website được thiết kế riêng và dựng trên nền tảng dễ mở rộng, có thể tham khảo dịch vụ thiết kế website của Hozitech.
Về tác giả
Founder
Xin chào mình là Hiền hiện đang là Founder của Hozitech. Mình là kỹ sư phần mềm có hơn 9+ năm kinh nghiệm trong lĩnh vực thiết kế website, ứng dụng di động... Gần đây mình đang nghiên cứu thêm về lĩnh vực marketing. Mình rất vui khi được chia sẽ những kiến thức và kinh nghiệm của mình đến mọi người!
Câu hỏi thường gặp