SEO fixes: robots.txt, canonicals, noindex, og tags, breadcrumbs, similar games, review schema
- robots.txt: proper Disallow rules + sitemap URL - Layout: robots meta tag, fix canonical syntax, og:site_name, full Twitter Card tags - Game pages: canonical URLs, og:type website, breadcrumb nav + JSON-LD schema - Screenshot alt text with game title and index - VideoGame JSON-LD: review array with rating/author/body - Similar Games section on game detail (by genre, ordered by rating) - browse/index: noindex on paginated or multi-filter pages - search results: noindex + improved title - User library and lists: noindex - Unique meta descriptions on browse, top-rated, new-releases pages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
bdb4457e77
commit
cdf17f92d7
@@ -39,7 +39,10 @@ class GameController extends Controller
|
|||||||
$genres = Genre::orderBy('name')->get();
|
$genres = Genre::orderBy('name')->get();
|
||||||
$platforms = Platform::orderBy('name')->get();
|
$platforms = Platform::orderBy('name')->get();
|
||||||
|
|
||||||
return view('games.index', compact('games', 'genres', 'platforms'));
|
$shouldNoindex = ($request->get('page', 1) > 1) ||
|
||||||
|
(collect(['genre', 'platform', 'year', 'sort', 'q'])->filter(fn($k) => $request->filled($k))->count() >= 2);
|
||||||
|
|
||||||
|
return view('games.index', compact('games', 'genres', 'platforms', 'shouldNoindex'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Game $game)
|
public function show(Game $game)
|
||||||
@@ -61,7 +64,18 @@ class GameController extends Controller
|
|||||||
->limit(20)
|
->limit(20)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$response = view('games.show', compact('game', 'userGame', 'userReview', 'userLists', 'comments'));
|
$similarGames = collect();
|
||||||
|
if ($game->genres->isNotEmpty()) {
|
||||||
|
$similarGames = Game::published()
|
||||||
|
->whereHas('genres', fn($q) => $q->whereIn('genres.id', $game->genres->pluck('id')))
|
||||||
|
->where('id', '!=', $game->id)
|
||||||
|
->where('rating_count', '>=', 1)
|
||||||
|
->orderByDesc('rating_avg')
|
||||||
|
->limit(6)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = view('games.show', compact('game', 'userGame', 'userReview', 'userLists', 'comments', 'similarGames'));
|
||||||
return auth()->check()
|
return auth()->check()
|
||||||
? $response
|
? $response
|
||||||
: response($response)->header('Cache-Control', 'public, max-age=300');
|
: response($response)->header('Cache-Control', 'public, max-age=300');
|
||||||
|
|||||||
+13
-1
@@ -1,6 +1,18 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Allow: /
|
Allow: /
|
||||||
Disallow: /profile
|
|
||||||
Disallow: /dashboard
|
Disallow: /dashboard
|
||||||
|
Disallow: /profile
|
||||||
|
Disallow: /library
|
||||||
|
Disallow: /notifications
|
||||||
|
Disallow: /activity
|
||||||
|
Disallow: /login
|
||||||
|
Disallow: /register
|
||||||
|
Disallow: /forgot-password
|
||||||
|
Disallow: /reset-password
|
||||||
|
Disallow: /verify-email
|
||||||
|
Disallow: /games/search
|
||||||
|
Disallow: /lists
|
||||||
|
Disallow: /*?*page=*
|
||||||
|
|
||||||
Sitemap: https://gamelog.ro/sitemap.xml
|
Sitemap: https://gamelog.ro/sitemap.xml
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', 'Browse Games — GameLog')
|
@section('title', 'Browse Games — GameLog')
|
||||||
|
@section('description', 'Browse and discover video games. Filter by genre, platform, and year. Find your next favorite game on GameLog.')
|
||||||
|
@section('canonical', route('games.index'))
|
||||||
|
@if($shouldNoindex ?? false)
|
||||||
|
@section('robots', 'noindex,follow')
|
||||||
|
@endif
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', 'New Releases — GameLog')
|
@section('title', 'New Game Releases — GameLog')
|
||||||
|
@section('description', 'Browse the latest video game releases. Stay up to date with new games across all platforms.')
|
||||||
|
@section('canonical', route('games.new-releases'))
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', $q ? "Search: {$q} — GameLog" : 'Search Games — GameLog')
|
@section('title', $q ? "Search Results for \"{$q}\" — GameLog" : 'Search Games — GameLog')
|
||||||
|
@section('robots', 'noindex,follow')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|||||||
@@ -6,9 +6,19 @@
|
|||||||
@section('og_title', $game->title . ' — GameLog')
|
@section('og_title', $game->title . ' — GameLog')
|
||||||
@section('og_description', $game->summary ? Str::limit($game->summary, 155) : 'Track ' . $game->title . ' on GameLog.')
|
@section('og_description', $game->summary ? Str::limit($game->summary, 155) : 'Track ' . $game->title . ' on GameLog.')
|
||||||
@section('og_image', $game->cover_url ?? asset('images/og-default.jpg'))
|
@section('og_image', $game->cover_url ?? asset('images/og-default.jpg'))
|
||||||
@section('og_type', 'game')
|
@section('og_type', 'website')
|
||||||
|
@section('canonical', route('games.show', $game))
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
<nav class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-6 pb-0" aria-label="Breadcrumb">
|
||||||
|
<ol class="flex items-center gap-1.5 text-sm text-slate-500">
|
||||||
|
<li><a href="{{ route('home') }}" class="hover:text-slate-300 transition-colors">Home</a></li>
|
||||||
|
<li class="text-slate-700">/</li>
|
||||||
|
<li><a href="{{ route('games.index') }}" class="hover:text-slate-300 transition-colors">Games</a></li>
|
||||||
|
<li class="text-slate-700">/</li>
|
||||||
|
<li class="text-slate-400 truncate max-w-xs">{{ $game->title }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|
||||||
{{-- Main Info --}}
|
{{-- Main Info --}}
|
||||||
@@ -269,7 +279,7 @@
|
|||||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||||
@foreach($game->screenshots as $i => $shot)
|
@foreach($game->screenshots as $i => $shot)
|
||||||
<button @click="openAt({{ $i }})" class="block w-full rounded-lg overflow-hidden bg-[#16161d] hover:ring-1 hover:ring-purple-500 transition-all">
|
<button @click="openAt({{ $i }})" class="block w-full rounded-lg overflow-hidden bg-[#16161d] hover:ring-1 hover:ring-purple-500 transition-all">
|
||||||
<img src="{{ $shot->thumb_url ?? $shot->url }}" alt="Screenshot" class="w-full aspect-video object-cover">
|
<img src="{{ $shot->thumb_url ?? $shot->url }}" alt="{{ $game->title }} screenshot {{ $loop->iteration }}" class="w-full aspect-video object-cover">
|
||||||
</button>
|
</button>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@@ -304,7 +314,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{{-- Full-size image --}}
|
{{-- Full-size image --}}
|
||||||
<img :src="screenshots[current]?.url" alt="Screenshot" class="max-h-[85vh] max-w-[90vw] rounded-lg shadow-2xl object-contain">
|
<img :src="screenshots[current]?.url" :alt="`{{ addslashes($game->title) }} screenshot ` + (current + 1)" class="max-h-[85vh] max-w-[90vw] rounded-lg shadow-2xl object-contain">
|
||||||
|
|
||||||
{{-- Next --}}
|
{{-- Next --}}
|
||||||
<button @click="next()" class="absolute right-4 top-1/2 -translate-y-1/2 text-white/70 hover:text-white transition-colors p-2">
|
<button @click="next()" class="absolute right-4 top-1/2 -translate-y-1/2 text-white/70 hover:text-white transition-colors p-2">
|
||||||
@@ -542,10 +552,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if($similarGames->isNotEmpty())
|
||||||
|
<div class="mt-10 pt-8 border-t border-white/5">
|
||||||
|
<h2 class="text-lg font-semibold text-white mb-4">You Might Also Like</h2>
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
||||||
|
@foreach($similarGames as $similar)
|
||||||
|
<x-game-card :game="$similar" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@@context": "https://schema.org",
|
||||||
|
"@@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{"@@type": "ListItem", "position": 1, "name": "Home", "item": "{{ url('/') }}"},
|
||||||
|
{"@@type": "ListItem", "position": 2, "name": "Games", "item": "{{ route('games.index') }}"},
|
||||||
|
{"@@type": "ListItem", "position": 3, "name": "{{ addslashes($game->title) }}", "item": "{{ route('games.show', $game) }}"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@@context": "https://schema.org",
|
"@@context": "https://schema.org",
|
||||||
"@@type": "VideoGame",
|
"@@type": "VideoGame",
|
||||||
@@ -567,6 +599,21 @@
|
|||||||
"worstRating": 1
|
"worstRating": 1
|
||||||
}
|
}
|
||||||
@endif
|
@endif
|
||||||
|
@if($game->reviews->isNotEmpty())
|
||||||
|
,"review": [
|
||||||
|
@foreach($game->reviews->take(5) as $r)
|
||||||
|
@if($r->rating)
|
||||||
|
{
|
||||||
|
"@@type": "Review",
|
||||||
|
"author": {"@@type": "Person", "name": "{{ addslashes($r->user->name ?? 'Anonymous') }}"},
|
||||||
|
"reviewRating": {"@@type": "Rating", "ratingValue": "{{ $r->rating }}", "bestRating": "10", "worstRating": "1"},
|
||||||
|
"reviewBody": "{{ addslashes(Str::limit(strip_tags($r->body), 200)) }}",
|
||||||
|
"datePublished": "{{ $r->created_at->toDateString() }}"
|
||||||
|
}{{ !$loop->last ? ',' : '' }}
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
]
|
||||||
|
@endif
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', 'Top Rated Games — GameLog')
|
@section('title', 'Top Rated Games — GameLog')
|
||||||
|
@section('description', 'Discover the highest rated video games of all time, ranked by GameLog community ratings.')
|
||||||
|
@section('canonical', route('games.top-rated'))
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<meta name="robots" content="@yield('robots', 'index,follow')">
|
||||||
<title>@yield('title', 'GameLog — Track Every Game You Play')</title>
|
<title>@yield('title', 'GameLog — Track Every Game You Play')</title>
|
||||||
<meta name="description" content="@yield('description', 'GameLog lets you track every game you play, rate your favorites, and discover what to play next.')">
|
<meta name="description" content="@yield('description', 'GameLog lets you track every game you play, rate your favorites, and discover what to play next.')">
|
||||||
@hasSection('canonical')
|
@if(View::hasSection('canonical'))
|
||||||
<link rel="canonical" href="@yield('canonical')">
|
<link rel="canonical" href="@yield('canonical')">
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@@ -16,7 +17,11 @@
|
|||||||
<meta property="og:image" content="@yield('og_image', asset('images/og-default.jpg'))">
|
<meta property="og:image" content="@yield('og_image', asset('images/og-default.jpg'))">
|
||||||
<meta property="og:type" content="@yield('og_type', 'website')">
|
<meta property="og:type" content="@yield('og_type', 'website')">
|
||||||
<meta property="og:url" content="@yield('og_url', url()->current())">
|
<meta property="og:url" content="@yield('og_url', url()->current())">
|
||||||
|
<meta property="og:site_name" content="GameLog">
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:title" content="@yield('title', 'GameLog — Track Every Game You Play')">
|
||||||
|
<meta name="twitter:description" content="@yield('description', 'GameLog lets you track every game you play, rate your favorites, and discover what to play next.')">
|
||||||
|
<meta name="twitter:image" content="@yield('og_image', asset('images/og-default.jpg'))">
|
||||||
|
|
||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@php use Illuminate\Support\Str; @endphp
|
@php use Illuminate\Support\Str; @endphp
|
||||||
|
|
||||||
@section('title', 'My Lists — GameLog')
|
@section('title', 'My Lists — GameLog')
|
||||||
|
@section('robots', 'noindex,follow')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', ($user->slug ?? $user->name) . "'s Library — GameLog")
|
@section('title', ($user->slug ?? $user->name) . "'s Library — GameLog")
|
||||||
|
@section('robots', 'noindex,follow')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', ($user->slug ?? $user->name) . ' — GameLog')
|
@section('title', ($user->slug ?? $user->name) . ' — GameLog')
|
||||||
|
@section('canonical', route('users.show', $user->slug))
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||||
|
|||||||
Reference in New Issue
Block a user