Google Rich Results Uyumlu Yapılandırılmış Veri Planlaması
Muzibu müzik platformunun tüm içerikleri (şarkılar, albümler, sanatçılar, playlist'ler vb.) Google'ın anlayabileceği özel bir formata dönüştürülecek. Böylece Google arama sonuçlarında müzikleriniz daha güzel görünecek, yıldız puanları gösterilecek ve kullanıcılar şarkılarınızı daha kolay bulacak.
Kullanıcıya Faydası:
Blog modülünde kullanılan HasUniversalSchemas
trait pattern'i Muzibu modülünün tüm model'lerine uygulanacak. Her model için uygun schema.org
type'ları implement edilecek.
Teknik Stack:
Blog modülü zaten HasUniversalSchemas trait'ini kullanıyor ve başarıyla çalışıyor. Bu pattern Muzibu modülüne adapte edilecek.
Blog.php'deki Kullanım:
use HasUniversalSchemas;
// Database'de mevcut
protected $fillable = ['faq_data', 'howto_data'];
protected $casts = ['faq_data' => 'array', 'howto_data' => 'array'];
// Schema metodları
public function getAllSchemas(): array {
return [
'blogposting' => $this->getSchemaMarkup(),
'breadcrumb' => $this->getBreadcrumbSchema(),
'faq' => $this->getFaqSchema(),
'howto' => $this->getHowToSchema(),
];
}
| Model | HasSeo | HasUniversalSchemas | faq/howto Fields | Schema Method |
|---|---|---|---|---|
| Song | ✅ Var | ❌ Yok | ❌ Yok | getSeoFallbackSchemaMarkup() |
| Album | ✅ Var | ❌ Yok | ❌ Yok | getSeoFallbackSchemaMarkup() |
| Artist | ✅ Var | ❌ Yok | ❌ Yok | getSeoFallbackSchemaMarkup() |
| Playlist | ✅ Var | ❌ Yok | ❌ Yok | ❌ Yok |
| Genre | ✅ Var | ❌ Yok | ❌ Yok | getSeoFallbackSchemaMarkup() |
| Sector | ❌ Yok | ❌ Yok | ❌ Yok | ❌ Yok |
| Radio | ✅ Var | ❌ Yok | ❌ Yok | getSeoFallbackSchemaMarkup() |
Özet:
Her Muzibu model'i için uygun schema.org type ve özellikler belirlendi.
Database Fields:
Relations:
Schema.org JSON-LD Örneği:
{
"@context": "https://schema.org",
"@type": "MusicRecording",
"name": "Şarkı Başlığı",
"duration": "PT180S",
"url": "https://muzibu.com/songs/sarki-basligi",
"image": "https://cdn.muzibu.com/covers/song.jpg",
"byArtist": {
"@type": "MusicGroup",
"name": "Sanatçı Adı",
"url": "https://muzibu.com/artists/sanatci-adi"
},
"inAlbum": {
"@type": "MusicAlbum",
"name": "Albüm Adı",
"url": "https://muzibu.com/albums/album-adi"
},
"genre": "Pop",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.5",
"reviewCount": 120,
"bestRating": "5",
"worstRating": "1"
}
}
Ek Schema'lar:
Database Fields:
Relations:
Schema.org JSON-LD Örneği:
{
"@context": "https://schema.org",
"@type": "MusicAlbum",
"name": "Albüm Başlığı",
"description": "Albüm açıklaması",
"url": "https://muzibu.com/albums/album-basligi",
"image": "https://cdn.muzibu.com/covers/album.jpg",
"byArtist": {
"@type": "MusicGroup",
"name": "Sanatçı Adı",
"url": "https://muzibu.com/artists/sanatci-adi"
},
"numTracks": 12,
"track": [
{
"@type": "MusicRecording",
"name": "Şarkı 1",
"duration": "PT180S",
"position": 1
}
],
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": 85
}
}
Ek Schema'lar:
Database Fields:
Relations:
Schema.org JSON-LD Örneği:
{
"@context": "https://schema.org",
"@type": "MusicGroup",
"name": "Sanatçı Adı",
"description": "Sanatçı biyografisi",
"url": "https://muzibu.com/artists/sanatci-adi",
"image": "https://cdn.muzibu.com/photos/artist.jpg",
"genre": ["Pop", "Rock"],
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9",
"reviewCount": 230
},
"album": [
{
"@type": "MusicAlbum",
"name": "Albüm 1",
"url": "https://muzibu.com/albums/album-1"
}
]
}
Ek Schema'lar:
Database Fields:
Relations:
Schema.org JSON-LD Örneği:
{
"@context": "https://schema.org",
"@type": "MusicPlaylist",
"name": "Playlist Başlığı",
"description": "Playlist açıklaması",
"url": "https://muzibu.com/playlists/playlist-basligi",
"numTracks": 25,
"track": [
{
"@type": "MusicRecording",
"name": "Şarkı 1",
"position": 1
}
]
}
Ek Schema'lar:
Database Fields:
Relations:
Schema.org JSON-LD Örneği:
{
"@context": "https://schema.org",
"@type": "DefinedTerm",
"name": "Pop",
"description": "Popüler müzik türü",
"url": "https://muzibu.com/genres/pop",
"inDefinedTermSet": {
"@type": "DefinedTermSet",
"name": "Müzik Türleri"
}
}
Ek Schema'lar:
Database Fields:
Relations:
Schema.org JSON-LD Örneği:
{
"@context": "https://schema.org",
"@type": "RadioStation",
"name": "Radyo Adı",
"url": "https://muzibu.com/radios/radyo-adi",
"image": "https://cdn.muzibu.com/logos/radio.jpg",
"broadcastServiceTier": "free"
}
Not: Sector, iş sektörü kategorisi olduğu için müzik ile ilgili değil. Bu model için genel DefinedTerm kullanılacak veya schema implementasyonu opsiyonel olabilir.
Schema.org JSON-LD Örneği:
{
"@context": "https://schema.org",
"@type": "DefinedTerm",
"name": "Otel",
"description": "Otelcilik sektörü",
"inDefinedTermSet": {
"@type": "DefinedTermSet",
"name": "İş Sektörleri"
}
}
Song, Album, Artist, Playlist model'lerine faq_data
ve howto_data field'larını ekle.
Migration Dosya Konumu:
Modules/Muzibu/database/migrations/2026_01_09_add_schema_fields_to_muzibu_tables.php
Modules/Muzibu/database/migrations/tenant/2026_01_09_add_schema_fields_to_muzibu_tables.php
Migration SQL:
// muzibu_songs tablosu
ALTER TABLE muzibu_songs
ADD COLUMN faq_data JSON NULL AFTER lyrics,
ADD COLUMN howto_data JSON NULL AFTER faq_data;
// muzibu_albums tablosu
ALTER TABLE muzibu_albums
ADD COLUMN faq_data JSON NULL AFTER description,
ADD COLUMN howto_data JSON NULL AFTER faq_data;
// muzibu_artists tablosu
ALTER TABLE muzibu_artists
ADD COLUMN faq_data JSON NULL AFTER bio,
ADD COLUMN howto_data JSON NULL AFTER faq_data;
// muzibu_playlists tablosu
ALTER TABLE muzibu_playlists
ADD COLUMN faq_data JSON NULL AFTER description,
ADD COLUMN howto_data JSON NULL AFTER faq_data;
⚠️ Kritik: Migration dosyası hem central hem tenant klasörüne eklenecek!
Her model'e use HasUniversalSchemas; ekle
ve $fillable /
$casts array'lerini güncelle.
Örnek: Song.php
use App\Traits\HasUniversalSchemas;
class Song extends BaseModel implements TranslatableEntity, HasMedia
{
use Sluggable, HasTranslations, HasSeo, HasUniversalSchemas, ...;
protected $fillable = [
// ... mevcut field'lar
'faq_data',
'howto_data',
];
protected $casts = [
// ... mevcut cast'lar
'faq_data' => 'array',
'howto_data' => 'array',
];
}
Uygulanacak Model'ler:
Her model'de getAllSchemas()
ve getBreadcrumbSchema() method'larını override et.
Örnek: Song.php
/**
* Tüm schema'ları al (MusicRecording + Universal schemas)
*/
public function getAllSchemas(): array
{
$schemas = [];
// 1. MusicRecording Schema (Ana içerik)
$songSchema = $this->getSchemaMarkup();
if ($songSchema) {
$schemas['musicrecording'] = $songSchema;
}
// 2. Breadcrumb Schema
$breadcrumbSchema = $this->getBreadcrumbSchema();
if ($breadcrumbSchema) {
$schemas['breadcrumb'] = $breadcrumbSchema;
}
// 3. FAQ Schema (varsa)
$faqSchema = $this->getFaqSchema();
if ($faqSchema) {
$schemas['faq'] = $faqSchema;
}
// 4. HowTo Schema (varsa)
$howtoSchema = $this->getHowToSchema();
if ($howtoSchema) {
$schemas['howto'] = $howtoSchema;
}
return $schemas;
}
/**
* Generate BreadcrumbList Schema for Song
* Override from HasUniversalSchemas trait
*/
public function getBreadcrumbSchema(): ?array
{
$locale = app()->getLocale();
$breadcrumbs = [];
$position = 1;
// 1. Home
$breadcrumbs[] = [
'@type' => 'ListItem',
'position' => $position++,
'name' => __('Ana Sayfa'),
'item' => url('/')
];
// 2. Şarkılar Ana Sayfa
$breadcrumbs[] = [
'@type' => 'ListItem',
'position' => $position++,
'name' => __('Şarkılar'),
'item' => url('/songs')
];
// 3. Current Song
$breadcrumbs[] = [
'@type' => 'ListItem',
'position' => $position,
'name' => $this->getTranslated('title', $locale),
'item' => $this->getUrl($locale)
];
return [
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => $breadcrumbs
];
}
Her Model İçin:
Her detay sayfasında (show.blade.php)
<head> içinde schema JSON-LD'yi ekle.
Örnek: songs/show.blade.php
@section('head')
@parent
{{-- Schema.org JSON-LD --}}
@php
$schemas = $song->getAllSchemas();
@endphp
@foreach($schemas as $key => $schema)
@endforeach
@endsection
Güncellenecek View Dosyaları:
Her sayfa için Google Rich Results Test ile doğrula.
Test Edilecek Sayfalar:
Beklenen Sonuçlar:
Modules/Muzibu/database/migrations/2026_01_09_add_schema_fields_to_muzibu_tables.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// muzibu_songs tablosu
if (Schema::hasTable('muzibu_songs')) {
Schema::table('muzibu_songs', function (Blueprint $table) {
if (!Schema::hasColumn('muzibu_songs', 'faq_data')) {
$table->json('faq_data')->nullable()->after('lyrics');
}
if (!Schema::hasColumn('muzibu_songs', 'howto_data')) {
$table->json('howto_data')->nullable()->after('faq_data');
}
});
}
// muzibu_albums tablosu
if (Schema::hasTable('muzibu_albums')) {
Schema::table('muzibu_albums', function (Blueprint $table) {
if (!Schema::hasColumn('muzibu_albums', 'faq_data')) {
$table->json('faq_data')->nullable()->after('description');
}
if (!Schema::hasColumn('muzibu_albums', 'howto_data')) {
$table->json('howto_data')->nullable()->after('faq_data');
}
});
}
// muzibu_artists tablosu
if (Schema::hasTable('muzibu_artists')) {
Schema::table('muzibu_artists', function (Blueprint $table) {
if (!Schema::hasColumn('muzibu_artists', 'faq_data')) {
$table->json('faq_data')->nullable()->after('bio');
}
if (!Schema::hasColumn('muzibu_artists', 'howto_data')) {
$table->json('howto_data')->nullable()->after('faq_data');
}
});
}
// muzibu_playlists tablosu
if (Schema::hasTable('muzibu_playlists')) {
Schema::table('muzibu_playlists', function (Blueprint $table) {
if (!Schema::hasColumn('muzibu_playlists', 'faq_data')) {
$table->json('faq_data')->nullable()->after('description');
}
if (!Schema::hasColumn('muzibu_playlists', 'howto_data')) {
$table->json('howto_data')->nullable()->after('faq_data');
}
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (Schema::hasTable('muzibu_songs')) {
Schema::table('muzibu_songs', function (Blueprint $table) {
$table->dropColumn(['faq_data', 'howto_data']);
});
}
if (Schema::hasTable('muzibu_albums')) {
Schema::table('muzibu_albums', function (Blueprint $table) {
$table->dropColumn(['faq_data', 'howto_data']);
});
}
if (Schema::hasTable('muzibu_artists')) {
Schema::table('muzibu_artists', function (Blueprint $table) {
$table->dropColumn(['faq_data', 'howto_data']);
});
}
if (Schema::hasTable('muzibu_playlists')) {
Schema::table('muzibu_playlists', function (Blueprint $table) {
$table->dropColumn(['faq_data', 'howto_data']);
});
}
}
};
⚠️ Kritik Hatırlatma:
migrations/ hem de
migrations/tenant/ klasörüne kopyala!php artisan migrate --forcephp artisan tenants:migrate --forceMigration'lar hem central hem tenant klasöründe olmalı!
Unutulursa sistem çöker. Her iki migration'ı da çalıştır.
Bu production sistemi! Backup al!
Migration öncesi database backup zorunlu.
JSON field'lar nullable olmalı!
faq_data ve howto_data opsiyonel, null olabilir.
Multi-language JSON field desteği
faq_data ve howto_data JSON field'larında çoklu dil desteği olmalı. HasUniversalSchemas trait bunu otomatik yapar.
Performance: Schema hesaplama maliyeti
getAllSchemas() her sayfa yüklenişinde çalışır. Cache düşün (gelecekte).
Test her model için ayrı ayrı yap
Song implement et → test et → Album'e geç.
Google Rich Results Test her zaman güncel değil
Bazen "hata yok" gösterir ama Google'da görünmeyebilir. Sabırlı ol.
faq_data ve howto_data opsiyonel
Kullanıcı doldurmak istemiyorsa boş bırakabilir, schema yine de çalışır.