Giới thiệu
Trong các dự án Laravel hiện đại, các nhà phát triển thường được khuyến khích sử dụng các khung giao diện người dùng nặng về JavaScript như Vue, React, Inertia.js hoặc Livewire. Trong khi những công cụ này rất hữu ích, chúng thường đi kèm với chi phí trí tuệ và kỹ thuật cao — đặc biệt khi mục tiêu của bạn rất đơn giản: gửi dữ liệu, nhận phản hồi và cập nhật một phần giao diện người dùng của bạn.
Tuy nhiên, hãy để tôi chia sẻ một điều từ hành trình phát triển của tôi.
Trở lại khi tôi làm việc với CodeIgniter, tôi đã theo một cách tiếp cận mà thật sự rất hiệu quả: Tôi sẽ trả về các phần HTML từ các API backend, và ở phía frontend, tôi đã sử dụng jQuery để chèn những phần này vào DOM. Điều này có nghĩa là các biểu mẫu sẽ trả về với các giá trị được cập nhật và các lỗi xác thực được hiển thị sẵn, giống như bạn mong đợi trong một ứng dụng nhiều trang truyền thống. Không cần phân tích JSON. Không cần vá DOM thủ công. Nó đơn giản hoạt động — và hoạt động rất tốt.
Vào thời điểm đó, phương pháp này hầu như bị bỏ qua vì xu hướng chuyển sang SPAs và JSON APIs. Nhưng ngày nay, trong cái mà tôi gọi là Web Transition 4, mô hình này đang trở lại mạnh mẽ. Chúng ta đang chứng kiến sự chuyển mình trở lại với việc trình bày giao diện người dùng từ phía máy chủ, nơi HTML lại một lần nữa là định dạng phản hồi chính, và JavaScript đóng vai trò là công cụ hỗ trợ — không phải là bộ trình bày cốt lõi.
Bài viết này sẽ hướng dẫn bạn cách áp dụng chính xác mô hình này trong Laravel hiện đại: trả về các phần Blade từ các controller và chỉ sử dụng một chút JavaScript (không phải một khung đầy đủ) để chèn chúng vào DOM.
Cách tiếp cận này đơn giản, thanh lịch và tận dụng những điểm mạnh của Laravel — mà không cần quản lý trạng thái frontend một cách thủ công.
Vấn đề với giao diện người dùng dựa trên JSON
Hãy xem xét một quy trình cơ bản:
- Bạn nhấp vào một nút hoặc gửi một biểu mẫu.
- Frontend gửi một yêu cầu fetch/axios đến một endpoint API Laravel.
- Laravel xử lý yêu cầu và trả về JSON.
- Frontend phân tích JSON.
- Nhà phát triển viết mã thao tác DOM để phản ánh trạng thái mới.
- Nhà phát triển xử lý thủ công các lỗi (như thông báo xác thực).
Nếu bạn đang nghĩ, "Đó là rất nhiều công việc cho một tương tác đơn giản," bạn đúng.
Càng tồi tệ hơn khi:
- Bạn có các điều kiện Blade phức tạp liên quan đến xác thực hoặc vai trò.
- Bạn phụ thuộc vào trạng thái phiên (mà các API không thể truy cập).
- Bạn kết thúc việc lặp lại logic rendering giữa PHP (đối với SSR) và JavaScript (đối với tính tương tác).
Vậy giải pháp thay thế là gì?
Mô hình API phần Blade
Thay vì JSON, controller Laravel của bạn trả về HTML đã được trình bày bằng Blade như là phản hồi. Sau đó, bạn chỉ cần thay thế một phần của trang của bạn bằng HTML này sử dụng JavaScript.
Dưới đây là cách nó hoạt động:
Định tuyến Laravel
// web.php
Route::get('/products',
[App\Http\Controllers\ProductController::class , 'index']
);
Phần Blade
<div class="px-4 pb-4 flex flex-col items-center justify-start border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<table class="min-w-full divide-y divide-gray-200 table-fixed dark:divide-gray-600">
<thead class="bg-gray-100 dark:bg-gray-700">
<tr>
<th scope="col" class="p-4 text-xs text-center font-medium text-gray-500 uppercase dark:text-gray-400 w-2/5">
Tiêu đề</th>
<th class="p-4 text-xs font-medium text-center text-gray-500 uppercase dark:text-gray-400 whitespace-nowrap ">
Danh mục</th>
<th scope="col" class="p-4 text-xs text-center font-medium text-gray-500 uppercase dark:text-gray-400">
Hành động</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200 dark:bg-gray-800 dark:divide-gray-700">
@if (isset($products) && count($products) > 0)
@foreach ($products as $product)
<tr class="hover:bg-gray-100 dark:hover:bg-gray-700">
<td class="p-4 text-sm font-normal text-gray-500 dark:text-gray-400">
{{ $product->title }}
</td>
<td class="p-4 text-sm font-normal text-gray-500 dark:text-gray-400">
{{ $product->category->name ?? 'Chưa phân loại' }}
</td>
<td class="p-4 space-x-2 whitespace-nowrap text-center">
<a href="{{ route('', [ 'products' => $id]) }}" title="Cập nhật">
Chỉnh sửa
</a>
<button type="button">
Xóa
</button>
</td>
</tr>
@endforeach
@else
@include('partials.norecord')
@endif
</tbody>
</table>
{{ $products->links('partials.paginator', ['data' => $products->toArray()]) }}
</div>
Controller
class ProductController extends Controller
{
public function index(Request $request)
{
$products = GetProducts::handle();
return view('products.index',['products' => $products]);
}
}
Mã trên sẽ tạo ra nội dung HTML với tất cả sức mạnh của Blade và phân trang được hiển thị.
JavaScript
async function getProducts() {
const response = await fetch('/products', {
method: 'GET',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
}
});
const html = await response.text();
document.querySelector('#products').innerHTML = html;
}
HTML đã được tải trước
<div id="products"></div>
Chỉ cần như vậy, bạn đã cập nhật một phần của trang mà không cần viết bất kỳ logic template JavaScript nào.
Tại sao điều này lại hoạt động tốt trong Laravel
Tự động hiển thị xác thực
Hãy tưởng tượng bạn gửi một biểu mẫu và xác thực của Laravel thất bại:
$request->validate([
'name' => 'required|min:3'
]);
Nếu bạn trả về một phần Blade bao gồm các khối @error, Laravel sẽ hiển thị chúng như vậy, không cần ánh xạ thủ công hoặc logic frontend nào.
<div class="col-span-6 sm:col-span-3">
<label for="name"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Tên</label>
<input form="user-form" type="text" name="name" id="name" required
value="{{ old('name', $user->name) }}"
/>
@error('name')
<p class="mt-2 text-sm text-red-600 dark:text-red-500">
<span class="font-medium">{{ $message }}</span>
</p>
@enderror
</div>
Và đây là kết quả.
Kiểm tra xác thực và vai trò hoạt động tốt
Điều này là một bước ngoặt.
Gần đây, khi làm việc trên một hệ thống quản lý vai trò, tôi đã phải hiển thị hoặc ẩn một số nút dựa trên vai trò người dùng:
@can('edit_users')
<button>Chỉnh sửa</button>
@endcan
Logic này hoạt động tốt trên các trang bình thường. Nhưng khi tôi chuyển điều này sang một route API (routes/api.php) và gọi nó qua fetch, đột nhiên các nút không xuất hiện — ngay cả cho admin.
Tại sao?
Bởi vì các route API mặc định không có trạng thái. Chúng không sử dụng guard phiên. Khi Laravel hiển thị view, không có người dùng đã xác thực, và kiểm tra vai trò thất bại.
Tôi đã dành hàng giờ để gỡ lỗi, chỉ để nhận ra rằng chuyển cùng logic đó sang một route web đã khắc phục nó ngay lập tức.
Với cách tiếp cận Blade này:
- Các phiên xác thực hoạt động
- Middleware áp dụng bình thường
- Rendering dựa trên vai trò hoạt động đúng như mong đợi
Các yếu tố bảo mật
Khi trả về các phần Blade từ Laravel, bạn vẫn có tất cả sức mạnh middleware như các route bình thường:
- Sử dụng middleware
authđể kiểm soát quyền truy cập. - Sử dụng các chỉ thị Blade như
@can,@auth, và các chỉ thị khác cho logic rendering. - Bảo vệ CSRF hoạt động ngay lập tức (miễn là bạn bao gồm token trong tiêu đề fetch của bạn).
Ví dụ:
Route::middleware('auth')->post('/dashboard-widgets', function () {
return view('partials.widgets', [...]);
});
Khi nào cách tiếp cận này tốt hơn Livewire hoặc Inertia?
Phương pháp này không thay thế mọi trường hợp sử dụng. Nhưng nó sáng giá khi:
- Ứng dụng của bạn chủ yếu được trình bày từ phía máy chủ.
- Bạn chỉ cần tính tương tác nhẹ (biểu mẫu, bộ lọc, tìm kiếm).
- Bạn muốn tránh chi phí của khung frontend.
- Bạn thích có một nguồn thông tin duy nhất cho giao diện của bạn: các view Blade của bạn.
Lợi ích so với Livewire
- Không có phép thuật hay tính năng ẩn.
- Không phụ thuộc vào Alpine.js.
- Toàn quyền kiểm soát khi và cách cập nhật DOM xảy ra.
Lợi ích so với Inertia
- Bạn không cần một khung JS (như Vue hoặc React).
- Bạn tránh việc liên kết chặt chẽ frontend của bạn với các route Laravel.
- Bạn bỏ qua lớp định tuyến phía khách hàng hoàn toàn.
Những suy nghĩ cuối cùng
Laravel là một khung phía máy chủ. Nó rực rỡ khi bạn để nó render các view.
Bằng cách trả về HTML thay vì JSON từ các API của bạn — và sử dụng các phần Blade thay vì các mẫu frontend — bạn:
- Đơn giản hóa ứng dụng của bạn
- Loại bỏ logic trùng lặp
- Giữ lại sức mạnh đầy đủ của Laravel (xác thực, vai trò, xác thực)
- Giảm thiểu các phụ thuộc frontend
Bạn không cần một khung JavaScript để xây dựng các giao diện tương tác.
Bạn chỉ cần lấy các phần — và để Laravel thực hiện công việc nặng nhọc.
Tóm tắt
- Ngừng quá phức tạp hóa frontend của bạn.
- Trả về các phần HTML từ Laravel, không phải JSON.
- Thay thế nội dung DOM bằng JavaScript đơn giản.
- Giữ logic của bạn ở một nơi: Blade.
- Tận hưởng sức mạnh đầy đủ của Laravel (xác thực, vai trò, xác thực) mà không phải đấu tranh với tính không trạng thái của API.
Nếu bạn thấy bài viết này hữu ích, hãy xem xét việc hỗ trợ công việc của tôi — điều đó có ý nghĩa rất nhiều.