From 15896248c4c7b9dd9814f8f70c35adadbd18496b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Wed, 20 May 2026 23:17:30 +0300 Subject: [PATCH] feat: enhance MusicProductionController with structured data support, improve SEO metadata handling, and add JSON-LD integration in views for better search engine visibility --- .../Controllers/MusicProductionController.php | 33 ++- app/Support/MusicProductionStructuredData.php | 225 ++++++++++++++++++ lang/en/music_productions.php | 1 + lang/tr/music_productions.php | 1 + .../views/components/seo/json-ld.blade.php | 7 + .../front/music-productions/index.blade.php | 100 ++++++-- .../front/music-productions/show.blade.php | 2 + resources/views/layouts/site.blade.php | 22 +- 8 files changed, 367 insertions(+), 24 deletions(-) create mode 100644 app/Support/MusicProductionStructuredData.php create mode 100644 resources/views/components/seo/json-ld.blade.php diff --git a/app/Http/Controllers/MusicProductionController.php b/app/Http/Controllers/MusicProductionController.php index 98347b6..d7567d1 100644 --- a/app/Http/Controllers/MusicProductionController.php +++ b/app/Http/Controllers/MusicProductionController.php @@ -6,6 +6,7 @@ use App\Models\MusicProduction; use App\Models\Setting; use App\Models\FooterTemplate; use App\Services\TemplateService; +use App\Support\MusicProductionStructuredData; use Illuminate\Support\Str; class MusicProductionController extends Controller @@ -60,12 +61,22 @@ class MusicProductionController extends Controller } } + $canonicalUrl = route('music-productions.index'); + $pageUrl = $search !== '' ? $canonicalUrl : url()->current(); + $meta = [ - 'title' => __('music_productions.nav-music-productions'), + 'title' => __('music_productions.meta-index-title'), 'description' => __('music_productions.meta-index-description'), 'image' => asset('assets/music-production.png'), + 'image_alt' => __('music_productions.meta-index-title'), + 'canonical' => $canonicalUrl, + 'robots' => $search !== '' ? 'noindex, follow' : 'index, follow', + 'og_type' => 'website', + 'locale' => app()->getLocale(), ]; + $structuredData = MusicProductionStructuredData::forIndex($productions, $pageUrl); + return view('front.music-productions.index', [ 'productions' => $productions, 'search' => $search, @@ -73,6 +84,7 @@ class MusicProductionController extends Controller 'header' => 'partials.header-center-nav', 'renderedFooter' => $renderedFooter, 'meta' => $meta, + 'structuredData' => $structuredData, ]); } @@ -132,12 +144,24 @@ class MusicProductionController extends Controller } } + $pageUrl = route('music-productions.show', $production->slug); + $title = $production->translate('title'); + $description = Str::limit(strip_tags((string) $production->translate('content')), 160) + ?: __('music_productions.meta-index-description'); + $meta = [ - 'title' => $production->translate('title'), - 'description' => Str::limit(strip_tags($production->translate('content')), 160), - 'image' => $production->cover_image_url, + 'title' => $title, + 'description' => $description, + 'image' => $production->cover_image_url ?: asset('assets/music-production.png'), + 'image_alt' => $title, + 'canonical' => $pageUrl, + 'robots' => 'index, follow', + 'og_type' => 'article', + 'locale' => app()->getLocale(), ]; + $structuredData = MusicProductionStructuredData::forShow($production, $pageUrl); + return view('front.music-productions.show', [ 'production' => $production, 'prev' => $prev, @@ -146,6 +170,7 @@ class MusicProductionController extends Controller 'header' => 'partials.header-center-nav', 'renderedFooter' => $renderedFooter, 'meta' => $meta, + 'structuredData' => $structuredData, ]); } } diff --git a/app/Support/MusicProductionStructuredData.php b/app/Support/MusicProductionStructuredData.php new file mode 100644 index 0000000..47ffed7 --- /dev/null +++ b/app/Support/MusicProductionStructuredData.php @@ -0,0 +1,225 @@ +currentPage() - 1) * $productions->perPage(); + + foreach ($productions as $index => $production) { + $itemListElements[] = [ + '@type' => 'ListItem', + 'position' => $offset + $index + 1, + 'item' => self::productionSchema($production), + ]; + } + + $graph = [ + self::websiteNode($siteUrl, $siteName), + self::organizationNode($siteUrl, $siteName), + [ + '@type' => 'CollectionPage', + '@id' => $pageUrl . '#webpage', + 'url' => $pageUrl, + 'name' => $pageName, + 'description' => $pageDescription, + 'inLanguage' => app()->getLocale(), + 'isPartOf' => ['@id' => $siteUrl . '#website'], + 'breadcrumb' => ['@id' => $pageUrl . '#breadcrumb'], + 'mainEntity' => ['@id' => $pageUrl . '#itemlist'], + ], + [ + '@type' => 'BreadcrumbList', + '@id' => $pageUrl . '#breadcrumb', + 'itemListElement' => [ + [ + '@type' => 'ListItem', + 'position' => 1, + 'name' => __('music_productions.breadcrumb_home'), + 'item' => $siteUrl, + ], + [ + '@type' => 'ListItem', + 'position' => 2, + 'name' => __('music_productions.nav-music-productions'), + 'item' => route('music-productions.index'), + ], + ], + ], + [ + '@type' => 'ItemList', + '@id' => $pageUrl . '#itemlist', + 'name' => $pageName, + 'numberOfItems' => $productions->total(), + 'itemListElement' => $itemListElements, + ], + ]; + + return [ + '@context' => 'https://schema.org', + '@graph' => $graph, + ]; + } + + public static function forShow(MusicProduction $production, string $pageUrl): array + { + $siteName = setting('site_name', config('app.name')); + $siteUrl = url('/'); + $title = $production->translate('title'); + $description = Str::limit(strip_tags((string) $production->translate('content')), 160); + + $graph = [ + self::websiteNode($siteUrl, $siteName), + self::organizationNode($siteUrl, $siteName), + [ + '@type' => 'BreadcrumbList', + '@id' => $pageUrl . '#breadcrumb', + 'itemListElement' => [ + [ + '@type' => 'ListItem', + 'position' => 1, + 'name' => __('music_productions.breadcrumb_home'), + 'item' => $siteUrl, + ], + [ + '@type' => 'ListItem', + 'position' => 2, + 'name' => __('music_productions.nav-music-productions'), + 'item' => route('music-productions.index'), + ], + [ + '@type' => 'ListItem', + 'position' => 3, + 'name' => $title, + 'item' => $pageUrl, + ], + ], + ], + array_merge( + [ + '@type' => 'WebPage', + '@id' => $pageUrl . '#webpage', + 'url' => $pageUrl, + 'name' => $title, + 'description' => $description, + 'inLanguage' => app()->getLocale(), + 'isPartOf' => ['@id' => $siteUrl . '#website'], + 'breadcrumb' => ['@id' => $pageUrl . '#breadcrumb'], + 'mainEntity' => ['@id' => $pageUrl . '#production'], + ], + $production->updated_at ? ['dateModified' => $production->updated_at->toIso8601String()] : [], + ), + array_merge( + ['@id' => $pageUrl . '#production'], + self::productionSchema($production), + ), + ]; + + return [ + '@context' => 'https://schema.org', + '@graph' => $graph, + ]; + } + + /** + * @return array + */ + protected static function productionSchema(MusicProduction $production): array + { + $schema = [ + '@type' => filled($production->spotify_album_id) ? 'MusicAlbum' : 'CreativeWork', + 'name' => $production->translate('title'), + 'url' => route('music-productions.show', $production->slug), + ]; + + if ($production->cover_image_url) { + $schema['image'] = $production->cover_image_url; + } + + $content = strip_tags((string) $production->translate('content')); + if ($content !== '') { + $schema['description'] = Str::limit($content, 300); + } + + if ($production->production_date) { + $schema['datePublished'] = $production->production_date->toIso8601String(); + } + + $clientName = $production->translate('client_name'); + if ($clientName) { + $schema['creator'] = [ + '@type' => 'Organization', + 'name' => $clientName, + ]; + } + + $sameAs = array_values(array_filter([ + $production->spotify_url, + $production->youtube_url, + ])); + + if ($sameAs !== []) { + $schema['sameAs'] = $sameAs; + } + + return $schema; + } + + /** + * @return array + */ + protected static function websiteNode(string $siteUrl, string $siteName): array + { + return [ + '@type' => 'WebSite', + '@id' => $siteUrl . '#website', + 'url' => $siteUrl, + 'name' => $siteName, + 'publisher' => ['@id' => $siteUrl . '#organization'], + 'potentialAction' => [ + '@type' => 'SearchAction', + 'target' => [ + '@type' => 'EntryPoint', + 'urlTemplate' => route('music-productions.index') . '?q={search_term_string}', + ], + 'query-input' => 'required name=search_term_string', + ], + ]; + } + + /** + * @return array + */ + protected static function organizationNode(string $siteUrl, string $siteName): array + { + $node = [ + '@type' => 'Organization', + '@id' => $siteUrl . '#organization', + 'name' => $siteName, + 'url' => $siteUrl, + ]; + + $logo = setting('site_logo'); + if ($logo) { + $logoUrl = str_starts_with($logo, 'http') ? $logo : asset('storage/' . ltrim($logo, '/')); + $node['logo'] = [ + '@type' => 'ImageObject', + 'url' => $logoUrl, + ]; + } + + return $node; + } +} diff --git a/lang/en/music_productions.php b/lang/en/music_productions.php index 41ebb30..e91a256 100644 --- a/lang/en/music_productions.php +++ b/lang/en/music_productions.php @@ -3,6 +3,7 @@ return [ // Navigation & list 'nav-music-productions' => 'Music Productions', + 'breadcrumb_home' => 'Home', 'meta-index-subtitle' => 'Truncgil', 'meta-index-title' => 'Music production, soundtracks and corporate music projects.', 'meta-index-description' => 'Explore our albums, singles and production work published on Spotify, YouTube and digital platforms.', diff --git a/lang/tr/music_productions.php b/lang/tr/music_productions.php index 24fbb83..ed09cf3 100644 --- a/lang/tr/music_productions.php +++ b/lang/tr/music_productions.php @@ -3,6 +3,7 @@ return [ // Navigation & list 'nav-music-productions' => 'Müzik Prodüksiyonları', + 'breadcrumb_home' => 'Ana Sayfa', 'meta-index-subtitle' => 'Truncgil', 'meta-index-title' => 'Müzik prodüksiyonu, film müzikleri ve kurumsal müzik çalışmalarımız.', 'meta-index-description' => 'Spotify, YouTube ve dijital platformlarda yayımlanan albüm, single ve prodüksiyon projelerimizi keşfedin.', diff --git a/resources/views/components/seo/json-ld.blade.php b/resources/views/components/seo/json-ld.blade.php new file mode 100644 index 0000000..87ad4b0 --- /dev/null +++ b/resources/views/components/seo/json-ld.blade.php @@ -0,0 +1,7 @@ +@props(['data' => null]) + +@if(!empty($data)) +@push('structured-data') + +@endpush +@endif diff --git a/resources/views/front/music-productions/index.blade.php b/resources/views/front/music-productions/index.blade.php index 5108b0c..11b1093 100644 --- a/resources/views/front/music-productions/index.blade.php +++ b/resources/views/front/music-productions/index.blade.php @@ -1,5 +1,7 @@ @extends('layouts.site') + + @section('content') {{-- demo28.html: hero + itemgrid --}}
@@ -24,23 +26,26 @@

{{ __('music_productions.meta-index-title') }}

-

{{ __('music_productions.meta-index-description') }}

- +

{{ __('music_productions.meta-index-description') }}

+
@@ -170,6 +175,67 @@ @push('styles')