Vấn đề
Trong các framework MVC (Yii, Laravel, CodeIgniter, v.v.), layout định nghĩa meta tag mặc định. View con cần ghi đè (override) các meta tag này (title, description, og:tags, v.v.).
Giải pháp pattern: Register Meta Tags
Cách phổ biến: Layout đăng ký meta tag mặc định, View ghi đè bằng key tương tự.
Ví dụ với PHP thuần / Framework tùy chỉnh
1. Config mặc định (config/meta.php)
<?php
return [
'default_meta' => [
'author' => 'Demo Company',
'description' => 'Mô tả mặc định cho website',
'keywords' => 'web, development, html5',
'og:title' => 'Demo Website',
'og:description' => 'Mô tả mặc định cho social share',
'og:image' => '/assets/images/default-og.jpg',
'og:type' => 'website',
'twitter:card' => 'summary_large_image',
]
];
2. Layout (views/layouts/main.php)
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?php
// Load config mặc định
$defaultMeta = require __DIR__ . '/../../config/meta.php';
$meta = $defaultMeta['default_meta'];
// Merge với meta từ view (nếu có)
if (isset($this->meta) && is_array($this->meta)) {
$meta = array_merge($meta, $this->meta);
}
// Render meta tags
foreach ($meta as $name => $content) {
$attr = strpos($name, ':') !== false ? 'property' : 'name';
echo "<meta $attr=\"$name\" content=\"" . htmlspecialchars($content, ENT_QUOTES, 'UTF-8') . "\">\n";
}
?>
<title><?= htmlspecialchars($this->title ?? 'Default Title') ?></title>
<!-- CSS/JS -->
</head>
<body>
<?= $content ?>
</body>
</html>
3. View con ghi đè (views/post/view.php)
<?php
// Ghi đè meta tags cho trang này
$this->meta = [
'description' => 'Chi tiết bài viết: ' . $post->excerpt,
'keywords' => implode(', ', $post->tags),
'og:title' => $post->title,
'og:description' => $post->excerpt,
'og:image' => $post->thumbnail_url,
'og:type' => 'article',
'article:published_time' => $post->created_at->format('c'),
'article:author' => $post->author->name,
];
$this->title = $post->title;
?>
<article>
<h1><?= htmlspecialchars($post->title) ?></h1>
<div><?= $post->content ?></div>
</article>
Cách làm với Yii2 (Framework phổ biến)
<?php // Layout: views/layouts/main.php use yii\helpers\Html; $this->registerMetaTag(['name' => 'author', 'content' => 'Default Author']); $this->registerMetaTag(['name' => 'description', 'content' => 'Default description']); $this->registerLinkTag(['rel' => 'canonical', 'href' => Yii::$app->request->absoluteUrl]); ?> <!DOCTYPE html> <html lang="<?= Yii::$app->language ?>"> <head> <meta charset="<?= Yii::$app->charset ?>"> <meta name="viewport" content="width=device-width, initial-scale=1"> <?= Html::csrfMetaTags() ?> <title><?= Html::encode($this->title) ?></title> <?php $this->head() ?> </head> <body> <?php $this->beginBody() ?> <?= $content ?> <?php $this->endBody() ?> </body> </html>
<?php
// View: views/post/view.php
use yii\helpers\Html;
$this->title = $post->title;
// Ghi đè meta tags
$this->registerMetaTag([
'name' => 'description',
'content' => $post->excerpt
], 'description'); // Key 'description' để ghi đè tag cùng name
$this->registerMetaTag([
'property' => 'og:title',
'content' => $post->title
], 'og:title');
$this->registerMetaTag([
'property' => 'og:description',
'content' => $post->excerpt
], 'og:description');
$this->registerMetaTag([
'property' => 'og:image',
'content' => $post->thumbnailUrl
], 'og:image');
$this->registerLinkTag([
'rel' => 'canonical',
'href' => $post->canonicalUrl
], 'canonical');
?>
Cách làm với Laravel (Blade)
<!-- layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Default meta -->
<meta name="description" content="{{ config('app.default_description') }}">
<meta property="og:title" content="{{ config('app.name') }}">
<!-- Override từ view -->
@stack('meta')
<title>@yield('title', config('app.name'))</title>
</head>
<body>
@yield('content')
</body>
</html>
<!-- views/post/show.blade.php -->
@extends('layouts.app')
@section('title', $post->title)
@push('meta')
<meta name="description" content="{{ $post->excerpt }}">
<meta property="og:title" content="{{ $post->title }}">
<meta property="og:description" content="{{ $post->excerpt }}">
<meta property="og:image" content="{{ $post->thumbnail_url }}">
<meta property="og:type" content="article">
<link rel="canonical" href="{{ $post->canonical_url }}">
@endpush
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<div>{{ $post->content }}</div>
</article>
@endsection
Best Practices
- Key-based override: Dùng key (name/property) để xác định tag nào bị ghi đè
- Merge strategy: Deep merge cho object, replace cho string
- Validation: Sanitize output (htmlspecialchars) để tránh XSS
- Canonical URL: Luôn set canonical để tránh duplicate content SEO
- Open Graph / Twitter Card: Đầy đủ cho social sharing