Halaman error bawaan Laravel itu rapi tapi anonim — “404, Not Found” tanpa identitas aplikasi. Situs toko buku milik Budi seharusnya menampilkan 404 yang tetap menyapa pembeli, bukan halaman generik pabrik. Kabar baiknya: mekanisme penggantian halaman error di Laravel tidak memerlukan kode sama sekali — cukup letakkan file Blade di tempat yang tepat dan framework akan menemukannya. Pada chapter ini kita bedah mekanisme resources/views/errors/, variabel $exception yang tersedia di dalamnya, penanganan rentang status 4xx/5xx, hingga halaman maintenance 503 dari perintah php artisan down.
Mekanisme errors/404.blade.php #
Saat handler harus merender HttpException, Laravel mencari view dengan pola errors::<kode-status>. Namespace errors:: dipetakan ke direktori resources/views/errors/, sehingga file resources/views/errors/404.blade.php otomatis dipakai untuk semua error 404:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Halaman tidak ditemukan</title>
</head>
<body>
<h1>404 — Halaman tidak ditemukan</h1>
<p>Budi mencari alamat <code>{{ request()->url() }}</code>, tetapi halaman tersebut tidak ada.</p>
<p>Pesan teknis: {{ $exception->getMessage() ?: '(tidak ada)' }}</p>
<a href="{{ url('/') }}">Kembali ke beranda</a>
</body>
</html>Di dalam view tersebut, variabel $exception tersedia gratis — kita bisa membaca getMessage() tanpa mengirim apa pun dari controller. Feature test berikut meminta route yang tidak ada dan mencetak response yang sebenarnya dikirim:
public function test_404_pakai_view_kustom(): void
{
$response = $this->get('/halaman-tidak-ada');
fwrite(STDERR, 'status: ' . $response->status() . "\n");
fwrite(STDERR, 'content_type: ' . $response->headers->get('Content-Type') . "\n");
fwrite(STDERR, "body:\n" . $response->getContent() . "\n");
$this->assertSame(404, $response->status());
}Output yang Dihasilkan:
status: 404
content_type: text/html; charset=utf-8
body:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Halaman tidak ditemukan</title>
</head>
<body>
<h1>404 — Halaman tidak ditemukan</h1>
<p>Budi mencari alamat <code>http://localhost:8000/halaman-tidak-ada</code>, tetapi halaman tersebut tidak ada.</p>
<p>Pesan teknis: The route halaman-tidak-ada could not be found.</p>
<a href="http://localhost:8000">Kembali ke beranda</a>
</body>
</html>Halaman kustom kita menggantikan halaman bawaan sepenuhnya, tetapi kode statusnya tetap 404 — penting agar crawler dan tool monitoring tetap membaca status yang benar.
Rentang 4xx dan 5xx #
Pencarian view tidak berhenti di kode persis. Jika errors::405 tidak ada, Laravel mencoba errors::4xx — satu view untuk seluruh rentang status. Kita uji dengan route yang memanggil abort(405, 'Metode tidak diizinkan.') dan file resources/views/errors/4xx.blade.php:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Permintaan ditolak</title>
</head>
<body>
<h1>Error {{ $exception->getStatusCode() }}</h1>
<p>Permintaan ditolak. Ini halaman rentang <code>4xx</code>.</p>
</body>
</html>Output yang Dihasilkan:
status: 405
body:
<!DOCTYPE html> <html lang="id"> <head> <meta charset="utf-8"> <title>Permintaan ditolak</title> </head> <body> <h1>Error 405</h1> <p>Permintaan ditolak. Ini halaman rentang <code>4xx</code>.</p> </body> </html>Exception Kustom dengan render() #
Cara lain menghasilkan halaman khusus adalah memberi exception class method render() sendiri. Pendekatan ini pas untuk kasus domain seperti “toko tutup” yang butuh halaman dan status tersendiri:
namespace App\Exceptions;
use Exception;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TokoTutupException extends Exception
{
public function render(Request $request): Response
{
return response()->view('errors.toko-tutup', [], 503);
}
}Output yang Dihasilkan (GET /toko/status yang melempar exception tersebut):
status: 503
body:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Toko sedang tutup</title>
</head>
<body>
<h1>Toko sedang tutup sementara</h1>
<p>Kami buka lagi besok pagi. Pesanan Budi aman.</p>
</body>
</html>Method render() dicek paling awal di pipeline, sehingga seluruh mekanisme default dilewati dan response kita yang menang.
Mode Maintenance 503 #
Perintah php artisan down menempatkan aplikasi ke mode maintenance: semua request mendapat 503 kecuali yang dikecualikan. Perintah tersebut menulis penanda JSON ke storage/framework/down:
{
"except": [
"\/up"
],
"redirect": null,
"retry": null,
"refresh": null,
"secret": null,
"status": 503,
"template": null
}Saat mode ini aktif, request GET ke / benar-benar membalas 503 dengan halaman bawaan “Service Unavailable” (judul <title>Service Unavailable</title>). Yang menarik, endpoint health /up masuk daftar except — tetap 200 selama maintenance karena alasan dijelaskan di chapter Monitoring Application Errors
. Perintah php artisan up menghapus penanda dan mengembalikan aplikasi normal. Opsi --secret juga tersedia untuk memberi akses maintenance rahasia lewat cookie.
Urutan pencarian view, dukungan $exception, dan mode maintenance membentuk lapisan presentasi error aplikasi kita. Selengkapnya terdokumentasi di dokumentasi Errors
. Berikutnya kita tinggalkan ranah HTTP dan masuk ke dapur: bagaimana log aplikasi dikonfigurasi di Logging Configuration
.