diff --git a/components/subscribe-newsletter.tsx b/components/subscribe-newsletter.tsx index 55cfc7e2..f1f4a16a 100644 --- a/components/subscribe-newsletter.tsx +++ b/components/subscribe-newsletter.tsx @@ -120,7 +120,10 @@ export default function SubscribeNewsletter(props: { isSmallScreen: Boolean }) { return (
- Subscribe to Keploy newsletter + Cartoon bunny mascot holding a letter near the Keploy newsletter signup form
({ + "@type": "ListItem", + position: index + 1, + item: { + "@type": "CreativeWork", + name: `${study.company} case study`, + description: study.outcome, + }, + })), + }, + }; + + return ( + + + {pageTitle} + +
+ +
+

Case Studies

+

{pageDescription}

+
    + {caseStudies.map((study) => ( +
  • +

    {study.company}

    +

    {study.outcome}

    +
  • + ))} +
+
+
+ + ); +} + +export async function getStaticProps({ preview = false }) { + return { + props: { + preview, + }, + revalidate: 600, + }; +} \ No newline at end of file diff --git a/pages/glossary/index.tsx b/pages/glossary/index.tsx new file mode 100644 index 00000000..d1bb38b1 --- /dev/null +++ b/pages/glossary/index.tsx @@ -0,0 +1,104 @@ +import Head from "next/head"; +import Layout from "../../components/layout"; +import Header from "../../components/header"; +import Container from "../../components/container"; +import { HOME_OG_IMAGE_URL } from "../../lib/constants"; +import { getBreadcrumbListSchema, SITE_URL } from "../../lib/structured-data"; + +const glossaryTerms = [ + { + name: "API Regression Testing", + description: + "A testing method that verifies API behavior remains stable after code changes, dependency updates, or infrastructure changes.", + }, + { + name: "Behavior Replay", + description: + "Reproducing recorded production API traffic in test environments to detect deviations before release.", + }, + { + name: "Test Generation", + description: + "Automatically creating test cases from observed API traffic, contracts, or source code context.", + }, + { + name: "Dependency Virtualization", + description: + "Simulating external services so APIs can be tested deterministically without brittle external dependencies.", + }, + { + name: "Flaky Test", + description: + "A non-deterministic test that passes or fails inconsistently without relevant code changes.", + }, + { + name: "Shift-Left Testing", + description: + "A practice of moving testing earlier into development and pull request workflows.", + }, +]; + +export default function GlossaryHub({ preview }) { + const pageUrl = `${SITE_URL}/glossary`; + const pageTitle = "Keploy API Testing Glossary"; + const pageDescription = + "Definitions of key API testing and quality engineering terms used across the Keploy ecosystem."; + + const definedTermSetSchema = { + "@context": "https://schema.org", + "@type": "DefinedTermSet", + name: "Keploy API Testing Glossary", + url: pageUrl, + hasDefinedTerm: glossaryTerms.map((term) => ({ + "@type": "DefinedTerm", + name: term.name, + description: term.description, + inDefinedTermSet: pageUrl, + })), + }; + + return ( + + + {pageTitle} + +
+ +
+

Glossary

+

{pageDescription}

+
+ {glossaryTerms.map((term) => ( +
+
{term.name}
+
{term.description}
+
+ ))} +
+
+
+ + ); +} + +export async function getStaticProps({ preview = false }) { + return { + props: { + preview, + }, + revalidate: 600, + }; +} \ No newline at end of file diff --git a/pages/integrations/index.tsx b/pages/integrations/index.tsx new file mode 100644 index 00000000..4e490ab5 --- /dev/null +++ b/pages/integrations/index.tsx @@ -0,0 +1,107 @@ +import Head from "next/head"; +import Layout from "../../components/layout"; +import Header from "../../components/header"; +import Container from "../../components/container"; +import { HOME_OG_IMAGE_URL } from "../../lib/constants"; +import { getBreadcrumbListSchema, SITE_URL } from "../../lib/structured-data"; + +const integrations = [ + { + name: "GitHub Actions", + summary: "Run Keploy API tests in CI with pull request checks and release pipelines.", + }, + { + name: "GitLab CI", + summary: "Validate API behavior as part of merge request and deployment workflows.", + }, + { + name: "Jenkins", + summary: "Integrate replay-based API regression tests into existing enterprise build jobs.", + }, + { + name: "Kubernetes", + summary: "Run Keploy in staging clusters for environment-safe traffic replay testing.", + }, + { + name: "Postman", + summary: "Bridge existing API collections with behavior-driven test generation.", + }, + { + name: "Docker", + summary: "Containerize test capture and replay workflows for consistent team usage.", + }, +]; + +export default function IntegrationsHub({ preview }) { + const pageUrl = `${SITE_URL}/integrations`; + const pageTitle = "Keploy Integrations"; + const pageDescription = + "Explore Keploy integrations for CI/CD, containers, cloud, and developer workflows to automate API testing at scale."; + + const collectionSchema = { + "@context": "https://schema.org", + "@type": "CollectionPage", + name: pageTitle, + url: pageUrl, + description: pageDescription, + mainEntity: { + "@type": "ItemList", + numberOfItems: integrations.length, + itemListElement: integrations.map((integration, index) => ({ + "@type": "ListItem", + position: index + 1, + item: { + "@type": "SoftwareApplication", + name: integration.name, + applicationCategory: "DeveloperApplication", + description: integration.summary, + }, + })), + }, + }; + + return ( + + + {pageTitle} + +
+ +
+

Integrations

+

{pageDescription}

+
    + {integrations.map((integration) => ( +
  • +

    {integration.name}

    +

    {integration.summary}

    +
  • + ))} +
+
+
+ + ); +} + +export async function getStaticProps({ preview = false }) { + return { + props: { + preview, + }, + revalidate: 600, + }; +} \ No newline at end of file diff --git a/pages/sitemap.xml.tsx b/pages/sitemap.xml.tsx index 613920dc..c6f44d15 100644 --- a/pages/sitemap.xml.tsx +++ b/pages/sitemap.xml.tsx @@ -73,15 +73,60 @@ function escapeXml(s: string): string { function buildSitemap(posts: PostNode[]): string { const today = new Date().toISOString().split("T")[0]; + const normalizeDate = (value?: string) => { + if (!value) return null; + const dateOnly = value.split("T")[0]; + return /^\d{4}-\d{2}-\d{2}$/.test(dateOnly) ? dateOnly : null; + }; + + const includedPosts = posts.filter((post) => + post.categories.edges.some((edge) => VALID_CATEGORIES.has(edge.node.slug)) + ); + + const allPostDates = includedPosts + .map((post) => normalizeDate(post.modified)) + .filter((value): value is string => Boolean(value)); + + const latestPostDate = + allPostDates.length > 0 + ? allPostDates.reduce((max, current) => (current > max ? current : max), allPostDates[0]) + : today; + + const latestByCategory = (categorySlug: string) => { + const dates = posts + .filter((post) => + post.categories.edges.some((edge) => edge.node.slug === categorySlug) + ) + .map((post) => normalizeDate(post.modified)) + .filter((value): value is string => Boolean(value)); + + if (dates.length === 0) { + return latestPostDate; + } + return dates.reduce((max, current) => (current > max ? current : max), dates[0]); + }; + const staticEntries = [ - { loc: `${MAIN_SITE_URL}/blog`, lastmod: today, priority: "1.00" }, - { loc: `${MAIN_SITE_URL}/blog/community`, lastmod: today, priority: "0.80" }, - { loc: `${MAIN_SITE_URL}/blog/technology`, lastmod: today, priority: "0.80" }, + { loc: `${MAIN_SITE_URL}/blog`, lastmod: latestPostDate, priority: "1.00" }, + { + loc: `${MAIN_SITE_URL}/blog/community`, + lastmod: latestByCategory("community"), + priority: "0.80", + }, + { + loc: `${MAIN_SITE_URL}/blog/technology`, + lastmod: latestByCategory("technology"), + priority: "0.80", + }, + { loc: `${MAIN_SITE_URL}/blog/integrations`, lastmod: latestPostDate, priority: "0.62" }, + { loc: `${MAIN_SITE_URL}/blog/solutions`, lastmod: latestPostDate, priority: "0.62" }, + { loc: `${MAIN_SITE_URL}/blog/case-studies`, lastmod: latestPostDate, priority: "0.62" }, + { loc: `${MAIN_SITE_URL}/blog/glossary`, lastmod: latestPostDate, priority: "0.62" }, ]; const postEntries: { loc: string; lastmod: string; priority: string }[] = []; const seen = new Set(); - for (const post of posts) { + for (const post of includedPosts) { const category = post.categories.edges .map((e) => e.node.slug) .find((slug) => VALID_CATEGORIES.has(slug)); @@ -91,7 +136,7 @@ function buildSitemap(posts: PostNode[]): string { seen.add(loc); postEntries.push({ loc, - lastmod: post.modified.split("T")[0], + lastmod: normalizeDate(post.modified) || today, priority: "0.64", }); } diff --git a/pages/solutions/index.tsx b/pages/solutions/index.tsx new file mode 100644 index 00000000..e23eb3da --- /dev/null +++ b/pages/solutions/index.tsx @@ -0,0 +1,102 @@ +import Head from "next/head"; +import Layout from "../../components/layout"; +import Header from "../../components/header"; +import Container from "../../components/container"; +import { HOME_OG_IMAGE_URL } from "../../lib/constants"; +import { getBreadcrumbListSchema, SITE_URL } from "../../lib/structured-data"; + +const solutions = [ + { + name: "Startups", + summary: "Ship faster with automated API regression tests that fit lean engineering teams.", + }, + { + name: "Scaleups", + summary: "Reduce release risk across microservices with behavior replay and test generation.", + }, + { + name: "Enterprise", + summary: "Add governance-friendly API testing with auditability, dedicated support, and controls.", + }, + { + name: "Developer Platforms", + summary: "Embed API quality gates into internal developer platforms and golden pipelines.", + }, + { + name: "Fintech and SaaS", + summary: "Validate critical transaction workflows with deterministic, production-like API tests.", + }, +]; + +export default function SolutionsHub({ preview }) { + const pageUrl = `${SITE_URL}/solutions`; + const pageTitle = "Keploy Solutions"; + const pageDescription = + "Discover Keploy solutions for startups, scaleups, and enterprise teams building reliable APIs and faster release pipelines."; + + const collectionSchema = { + "@context": "https://schema.org", + "@type": "CollectionPage", + name: pageTitle, + url: pageUrl, + description: pageDescription, + mainEntity: { + "@type": "ItemList", + numberOfItems: solutions.length, + itemListElement: solutions.map((solution, index) => ({ + "@type": "ListItem", + position: index + 1, + item: { + "@type": "Thing", + name: solution.name, + description: solution.summary, + }, + })), + }, + }; + + return ( + + + {pageTitle} + +
+ +
+

Solutions

+

{pageDescription}

+
    + {solutions.map((solution) => ( +
  • +

    {solution.name}

    +

    {solution.summary}

    +
  • + ))} +
+
+
+ + ); +} + +export async function getStaticProps({ preview = false }) { + return { + props: { + preview, + }, + revalidate: 600, + }; +} \ No newline at end of file diff --git a/tests/e2e/SeoMeta.spec.ts b/tests/e2e/SeoMeta.spec.ts index 1ff25cbd..7da6fe0a 100644 --- a/tests/e2e/SeoMeta.spec.ts +++ b/tests/e2e/SeoMeta.spec.ts @@ -124,6 +124,33 @@ test.describe('SEO and Meta Tags Configuration', () => { expect(body).toContain('https://keploy.io/blog'); expect(body).toContain('https://keploy.io/blog/community'); expect(body).toContain('https://keploy.io/blog/technology'); + expect(body).toContain('https://keploy.io/blog/integrations'); + expect(body).toContain('https://keploy.io/blog/solutions'); + expect(body).toContain('https://keploy.io/blog/case-studies'); + expect(body).toContain('https://keploy.io/blog/glossary'); + }); + + test('Glossary hub route should render with canonical metadata and DefinedTermSet schema', async ({ page, baseURL }) => { + const response = await page.goto(`${baseURL!}/glossary`); + expect(response?.status()).toBe(200); + await page.waitForLoadState('domcontentloaded'); + + const canonical = page.locator('link[rel="canonical"]'); + await expect(canonical).toBeAttached(); + await expect(canonical).toHaveAttribute('href', 'https://keploy.io/blog/glossary'); + + const schemas = page.locator('script[type="application/ld+json"]'); + const contents = await schemas.allTextContents(); + const hasDefinedTermSet = contents.some((content) => { + try { + const parsed = JSON.parse(content); + return parsed?.['@type'] === 'DefinedTermSet'; + } catch { + return false; + } + }); + + expect(hasDefinedTermSet).toBe(true); }); test('AI referral tracker should push event to dataLayer on UTM-attributed landing', async ({ page, baseURL }) => {