feat(seo): add eight landing pages for the clusters we had nothing for
Keyword research put the head demand on four phrases — clinic management software, doctor's office software, online booking, and the price/comparison questions that precede a purchase — and we only had a page for one of them. The new pages cover the head terms (clinic management, online booking), the buying intent (price, how to choose, download), the positioning that separates a hosted product from the installed ones that dominate these results (web based, no install), and two long-tails where competition is thin (clinic accounting, psychology practice). The download page deliberately answers the intent rather than the wording: someone searching for an installer wants to try something without paying, and the page gives them that while explaining why there is nothing to install. Copy is written per page rather than templated — eight pages saying the same thing in different words is duplicate content, and every claim on them maps to something the product actually does. Internal links from the older pages were repointed at the new head-term pages, and the registry tests no longer hardcode the page count: they now assert that every declared slug has a page, and that titles, headings and descriptions stay unique. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,9 @@ class LandingPageTest extends WebTestCase
|
||||
$xml = $this->html('/sitemap.xml');
|
||||
$doc = new \SimpleXMLElement($xml);
|
||||
|
||||
self::assertCount(8, $doc->url, 'sitemap باید خانه و هفت لندینگ را داشته باشد');
|
||||
// خانه + هر صفحهٔ فرود. عدد از خودِ رجیستری میآید تا افزودن خوشهٔ کلیدواژهٔ
|
||||
// تازه این تست را نشکند، ولی جاافتادن یک صفحه از sitemap هنوز قرمز شود.
|
||||
self::assertCount(1 + count($this->registry->all()), $doc->url);
|
||||
|
||||
foreach (array_keys($this->registry->all()) as $slug) {
|
||||
self::assertStringContainsString(rawurlencode($slug), $xml, "«{$slug}» در sitemap نیست");
|
||||
|
||||
@@ -7,7 +7,7 @@ use App\Shared\Landing\LandingRegistry;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* قواعدی که اگر بشکنند، هفت صفحهٔ فرود بهجای کمک به رتبه، به هم آسیب میزنند:
|
||||
* قواعدی که اگر بشکنند، صفحههای فرود بهجای کمک به رتبه، به هم آسیب میزنند:
|
||||
* عنوان یا h1 تکراری یعنی محتوای تکراری، و توضیحات بیرون از بازهٔ استاندارد در
|
||||
* نتایج جستجو بریده میشود.
|
||||
*/
|
||||
@@ -24,9 +24,15 @@ class LandingRegistryTest extends TestCase
|
||||
{
|
||||
$slugs = array_keys($this->registry->all());
|
||||
|
||||
self::assertCount(7, $slugs);
|
||||
self::assertContains(LandingRegistry::BEAUTY, $slugs);
|
||||
self::assertContains(LandingRegistry::CRM, $slugs);
|
||||
// تعداد صفحهها با هر خوشهٔ کلیدواژهٔ تازه بالا میرود، پس عدد ثابت اینجا فقط
|
||||
// یک تستِ شکننده میسازد. آنچه باید ثابت بماند این است که هر ثابتِ کلاس
|
||||
// واقعاً صفحهای دارد — ثابتی که ساخته نشده باشد، لینک داخلیِ ۴۰۴ میسازد.
|
||||
$constants = (new \ReflectionClass(LandingRegistry::class))->getConstants();
|
||||
|
||||
self::assertSame(count($constants), count($slugs));
|
||||
foreach ($constants as $name => $slug) {
|
||||
self::assertContains($slug, $slugs, "ثابت {$name} صفحهای ندارد");
|
||||
}
|
||||
}
|
||||
|
||||
public function testUnknownSlugReturnsNull(): void
|
||||
@@ -47,8 +53,12 @@ class LandingRegistryTest extends TestCase
|
||||
$titles = array_map(static fn (LandingPage $p): string => $p->metaTitle, $this->registry->all());
|
||||
$h1s = array_map(static fn (LandingPage $p): string => $p->h1, $this->registry->all());
|
||||
|
||||
self::assertCount(7, array_unique($titles), 'عنوان تکراری یعنی محتوای تکراری');
|
||||
self::assertCount(7, array_unique($h1s), 'h1 تکراری یعنی محتوای تکراری');
|
||||
$descriptions = array_map(static fn (LandingPage $p): string => $p->metaDescription, $this->registry->all());
|
||||
|
||||
self::assertCount(count($titles), array_unique($titles), 'عنوان تکراری یعنی محتوای تکراری');
|
||||
self::assertCount(count($h1s), array_unique($h1s), 'h1 تکراری یعنی محتوای تکراری');
|
||||
// توضیحات تکراری همان پیام را میدهد: دو صفحه که یک چیز را میگویند.
|
||||
self::assertCount(count($descriptions), array_unique($descriptions), 'توضیحات تکراری یعنی محتوای تکراری');
|
||||
}
|
||||
|
||||
/** توضیحات کوتاهتر از ۱۲۰ فرصت را هدر میدهد و بلندتر از ۱۷۰ در نتایج بریده میشود. */
|
||||
|
||||
Reference in New Issue
Block a user