Development of SEO Data Management System (Meta, OG)
An SEO data management system allows editors to set meta tags for each page — title, description, Open Graph, Twitter Card — without code changes. Critical for news sites, e-commerce stores, and corporate portals.
Data Model
seo_metas (
id, seo_type, seo_id, -- polymorphic relationship: Article, Product, Category, Page
title, description, keywords,
og_title, og_description, og_image_url,
twitter_title, twitter_description, twitter_image_url, twitter_card,
robots, -- 'index,follow' | 'noindex,nofollow' | custom
canonical_url, -- override automatic canonical
schema_type, -- type for JSON-LD
schema_data (jsonb),
created_at, updated_at
)
Trait for Models
trait HasSeoMeta
{
public function seoMeta(): MorphOne
{
return $this->morphOne(SeoMeta::class, 'seo');
}
public function getEffectiveSeoTitle(): string
{
return $this->seoMeta?->title
?? $this->getDefaultSeoTitle()
?? config('seo.site_name');
}
public function getEffectiveSeoDescription(): string
{
return $this->seoMeta?->description
?? $this->getDefaultSeoDescription()
?? '';
}
}
Meta Tag Component (Blade/React)
// Blade: layout.blade.php
<title>{{ $seo->title ?? config('seo.default_title') }}</title>
<meta name="description" content="{{ $seo->description ?? '' }}">
<meta name="robots" content="{{ $seo->robots ?? 'index,follow' }}">
@if($seo->canonical_url)
<link rel="canonical" href="{{ $seo->canonical_url }}">
@endif
<meta property="og:title" content="{{ $seo->og_title ?? $seo->title }}">
<meta property="og:description" content="{{ $seo->og_description ?? $seo->description }}">
<meta property="og:image" content="{{ $seo->og_image_url ?? config('seo.default_og_image') }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request()->url() }}">
<meta name="twitter:card" content="{{ $seo->twitter_card ?? 'summary_large_image' }}">
<meta name="twitter:title" content="{{ $seo->twitter_title ?? $seo->og_title ?? $seo->title }}">
<meta name="twitter:description" content="{{ $seo->twitter_description ?? $seo->og_description }}">
<meta name="twitter:image" content="{{ $seo->twitter_image_url ?? $seo->og_image_url }}">
Meta Tag Templates
For pages without manual settings — automatic generation from a template:
// For products
'{name} — buy for {price} ₽ | {site_name}'
'{description} Delivery throughout Russia.'
// Substitution
$title = Str::of(config('seo.product_title_template'))
->replace('{name}', $product->name)
->replace('{price}', number_format($product->price / 100, 0, '.', ' '))
->replace('{site_name}', config('seo.site_name'))
->limit(60);
CMS Edit Form
SEO section in the edit form for any entity — tab or accordion:
function SeoMetaForm({ data, onChange }) {
return (
<Accordion type="single" collapsible>
<AccordionItem value="seo">
<AccordionTrigger>SEO Settings</AccordionTrigger>
<AccordionContent>
<div className="space-y-4">
<div>
<Label>Title (recommended 50-60 characters)</Label>
<Input value={data.title} onChange={e => onChange({ title: e.target.value })} />
<CharCounter current={data.title?.length} max={60} />
</div>
<div>
<Label>Description (recommended 150-160 characters)</Label>
<Textarea value={data.description} onChange={e => onChange({ description: e.target.value })} />
<CharCounter current={data.description?.length} max={160} />
</div>
<SeoPreview title={data.title} description={data.description} />
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
);
}
The SeoPreview component shows how the page will appear in Google search results.
Development timeline: 2–3 days for a complete system with polymorphic relationships, templates, and edit form.







