diff --git a/app/Http/Controllers/Web/SitemapController.php b/app/Http/Controllers/Web/SitemapController.php
new file mode 100644
index 0000000..55d0942
--- /dev/null
+++ b/app/Http/Controllers/Web/SitemapController.php
@@ -0,0 +1,76 @@
+cacheService->remember(
+ 'sitemap:main',
+ 3600, // 1 hour cache
+ fn() => $this->sitemapService->generateSitemap()
+ );
+
+ return response($xml, 200)
+ ->header('Content-Type', 'application/xml');
+ }
+
+ /**
+ * Generate and serve sitemap index
+ */
+ public function sitemapIndex(): Response
+ {
+ $xml = $this->cacheService->remember(
+ 'sitemap:index',
+ 3600,
+ fn() => $this->sitemapService->generateSitemapIndex()
+ );
+
+ return response($xml, 200)
+ ->header('Content-Type', 'application/xml');
+ }
+
+ /**
+ * Generate and serve main sitemap (static pages)
+ */
+ public function mainSitemap(): Response
+ {
+ $xml = $this->cacheService->remember(
+ 'sitemap:main-pages',
+ 3600,
+ fn() => $this->sitemapService->generateMainSitemap()
+ );
+
+ return response($xml, 200)
+ ->header('Content-Type', 'application/xml');
+ }
+
+ /**
+ * Generate and serve jobs sitemap
+ */
+ public function jobsSitemap(): Response
+ {
+ $xml = $this->cacheService->remember(
+ 'sitemap:jobs',
+ 1800, // 30 minutes cache (jobs change more frequently)
+ fn() => $this->sitemapService->generateJobsSitemap()
+ );
+
+ return response($xml, 200)
+ ->header('Content-Type', 'application/xml');
+ }
+}
diff --git a/app/Services/SeoService.php b/app/Services/SeoService.php
new file mode 100644
index 0000000..7f815dd
--- /dev/null
+++ b/app/Services/SeoService.php
@@ -0,0 +1,285 @@
+title . ' at ' . $job->company . ' - JobifyGarden';
+ $description = Str::limit(strip_tags($job->description), 155);
+ $url = route('jobs.show', $job->id);
+
+ return [
+ 'title' => $title,
+ 'description' => $description,
+ 'keywords' => $this->generateJobKeywords($job),
+ 'canonical' => $url,
+ 'og_title' => $job->title . ' at ' . $job->company,
+ 'og_description' => $description,
+ 'og_url' => $url,
+ 'og_type' => 'website',
+ 'og_image' => asset('images/og-default.jpg'),
+ 'twitter_card' => 'summary_large_image',
+ 'twitter_title' => $job->title . ' at ' . $job->company,
+ 'twitter_description' => $description,
+ 'twitter_image' => asset('images/twitter-card-default.jpg'),
+ ];
+ }
+
+ /**
+ * Generate meta tags for category page
+ */
+ public function getCategoryMetaTags(JobCategory $category, int $jobCount): array
+ {
+ $title = $category->name . ' Jobs - JobifyGarden';
+ $description = "Browse {$jobCount} {$category->name} job opportunities. " . ($category->description ?? 'Find your next career in ' . $category->name . '.');
+ $url = route('jobs.category', $category->slug);
+
+ return [
+ 'title' => $title,
+ 'description' => Str::limit($description, 155),
+ 'keywords' => $this->generateCategoryKeywords($category),
+ 'canonical' => $url,
+ 'og_title' => $category->name . ' Jobs',
+ 'og_description' => Str::limit($description, 155),
+ 'og_url' => $url,
+ 'og_type' => 'website',
+ 'og_image' => asset('images/og-default.jpg'),
+ 'twitter_card' => 'summary',
+ 'twitter_title' => $category->name . ' Jobs',
+ 'twitter_description' => Str::limit($description, 155),
+ ];
+ }
+
+ /**
+ * Generate meta tags for homepage
+ */
+ public function getHomeMetaTags(int $jobCount): array
+ {
+ return [
+ 'title' => 'JobifyGarden - Find Your Dream Job | ' . number_format($jobCount) . '+ Jobs',
+ 'description' => 'Discover thousands of job opportunities from multiple sources. Search remote jobs, full-time positions, and more. Your next career starts here.',
+ 'keywords' => 'jobs, careers, employment, job search, remote jobs, full-time jobs, part-time jobs, find jobs',
+ 'canonical' => route('home'),
+ 'og_title' => 'JobifyGarden - Find Your Dream Job',
+ 'og_description' => 'Discover thousands of job opportunities from multiple sources. Your next career starts here.',
+ 'og_url' => route('home'),
+ 'og_type' => 'website',
+ 'og_image' => asset('images/og-home.jpg'),
+ 'twitter_card' => 'summary_large_image',
+ 'twitter_title' => 'JobifyGarden - Find Your Dream Job',
+ 'twitter_description' => 'Discover thousands of job opportunities from multiple sources.',
+ 'twitter_image' => asset('images/twitter-card-home.jpg'),
+ ];
+ }
+
+ /**
+ * Generate keywords for a job
+ */
+ private function generateJobKeywords(Job $job): string
+ {
+ $keywords = [
+ $job->title,
+ $job->company,
+ $job->job_type,
+ $job->experience_level,
+ ];
+
+ if ($job->category) {
+ $keywords[] = $job->category->name;
+ }
+
+ if ($job->location) {
+ $keywords[] = $job->location;
+ }
+
+ if ($job->remote_allowed) {
+ $keywords[] = 'remote';
+ $keywords[] = 'work from home';
+ }
+
+ return implode(', ', array_filter($keywords));
+ }
+
+ /**
+ * Generate keywords for a category
+ */
+ private function generateCategoryKeywords(JobCategory $category): string
+ {
+ return implode(', ', [
+ $category->name . ' jobs',
+ $category->name . ' careers',
+ $category->name . ' employment',
+ 'find ' . $category->name . ' jobs',
+ ]);
+ }
+
+ /**
+ * Generate JSON-LD structured data for a job
+ */
+ public function generateJobStructuredData(Job $job): array
+ {
+ $data = [
+ '@context' => 'https://schema.org/',
+ '@type' => 'JobPosting',
+ 'title' => $job->title,
+ 'description' => strip_tags($job->description),
+ 'datePosted' => $job->posted_at?->toIso8601String() ?? $job->created_at->toIso8601String(),
+ 'hiringOrganization' => [
+ '@type' => 'Organization',
+ 'name' => $job->company,
+ ],
+ 'jobLocation' => $this->getJobLocationData($job),
+ 'employmentType' => $this->mapEmploymentType($job->job_type),
+ ];
+
+ if ($job->expires_at) {
+ $data['validThrough'] = $job->expires_at->toIso8601String();
+ }
+
+ if ($job->salary_min || $job->salary_max) {
+ $data['baseSalary'] = $this->getSalaryData($job);
+ }
+
+ if ($job->experience_level) {
+ $data['experienceRequirements'] = [
+ '@type' => 'OccupationalExperienceRequirements',
+ 'monthsOfExperience' => $this->mapExperienceLevel($job->experience_level),
+ ];
+ }
+
+ if ($job->apply_url || $job->source_url) {
+ $data['url'] = $job->apply_url ?? $job->source_url;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get job location data for structured data
+ */
+ private function getJobLocationData(Job $job): array
+ {
+ if ($job->remote_allowed) {
+ return [
+ '@type' => 'Place',
+ 'address' => [
+ '@type' => 'PostalAddress',
+ 'addressCountry' => 'Remote',
+ ],
+ ];
+ }
+
+ $address = [
+ '@type' => 'PostalAddress',
+ ];
+
+ if ($job->jobLocation) {
+ $address['addressLocality'] = $job->jobLocation->city;
+ $address['addressRegion'] = $job->jobLocation->state;
+ $address['addressCountry'] = $job->jobLocation->country;
+ } elseif ($job->location) {
+ $address['name'] = $job->location;
+ }
+
+ return [
+ '@type' => 'Place',
+ 'address' => $address,
+ ];
+ }
+
+ /**
+ * Get salary data for structured data
+ */
+ private function getSalaryData(Job $job): array
+ {
+ $currency = $job->salary_currency ?? 'USD';
+
+ if ($job->salary_min && $job->salary_max) {
+ return [
+ '@type' => 'MonetaryAmount',
+ 'currency' => $currency,
+ 'value' => [
+ '@type' => 'QuantitativeValue',
+ 'minValue' => $job->salary_min,
+ 'maxValue' => $job->salary_max,
+ 'unitText' => 'YEAR',
+ ],
+ ];
+ }
+
+ $value = $job->salary_min ?? $job->salary_max;
+
+ return [
+ '@type' => 'MonetaryAmount',
+ 'currency' => $currency,
+ 'value' => [
+ '@type' => 'QuantitativeValue',
+ 'value' => $value,
+ 'unitText' => 'YEAR',
+ ],
+ ];
+ }
+
+ /**
+ * Map job type to schema.org employment type
+ */
+ private function mapEmploymentType(?string $jobType): string
+ {
+ return match ($jobType) {
+ 'full-time', 'full_time' => 'FULL_TIME',
+ 'part-time', 'part_time' => 'PART_TIME',
+ 'contract' => 'CONTRACTOR',
+ 'temporary' => 'TEMPORARY',
+ 'internship' => 'INTERN',
+ 'freelance' => 'CONTRACTOR',
+ default => 'FULL_TIME',
+ };
+ }
+
+ /**
+ * Map experience level to months
+ */
+ private function mapExperienceLevel(?string $level): int
+ {
+ return match ($level) {
+ 'entry' => 0,
+ 'mid' => 24,
+ 'senior' => 60,
+ 'lead' => 84,
+ 'executive' => 120,
+ default => 0,
+ };
+ }
+
+ /**
+ * Generate breadcrumb structured data
+ */
+ public function generateBreadcrumbStructuredData(array $items): array
+ {
+ $listItems = [];
+
+ foreach ($items as $index => $item) {
+ $listItems[] = [
+ '@type' => 'ListItem',
+ 'position' => $index + 1,
+ 'name' => $item['name'],
+ 'item' => $item['url'],
+ ];
+ }
+
+ return [
+ '@context' => 'https://schema.org',
+ '@type' => 'BreadcrumbList',
+ 'itemListElement' => $listItems,
+ ];
+ }
+}
diff --git a/app/Services/SitemapService.php b/app/Services/SitemapService.php
new file mode 100644
index 0000000..0372778
--- /dev/null
+++ b/app/Services/SitemapService.php
@@ -0,0 +1,167 @@
+