diff --git a/components/post-body.tsx b/components/post-body.tsx index 60890268..3ef5df04 100644 --- a/components/post-body.tsx +++ b/components/post-body.tsx @@ -237,9 +237,20 @@ export default function PostBody({ } const decodeHtmlEntities = (str: string): string => { - const textarea = document.createElement("textarea"); - textarea.innerHTML = str; - return textarea.value; + // document.createElement is browser-only; use a pure-JS fallback during SSR + if (typeof document !== "undefined") { + const textarea = document.createElement("textarea"); + textarea.innerHTML = str; + return textarea.value; + } + return str + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(/ /g, "\u00A0"); }; return safeContent @@ -407,4 +418,4 @@ export default function PostBody({ ); -} \ No newline at end of file +} diff --git a/pages/community/[slug].tsx b/pages/community/[slug].tsx index 6c0211a6..10ff7f1c 100644 --- a/pages/community/[slug].tsx +++ b/pages/community/[slug].tsx @@ -30,9 +30,7 @@ import { } from "../../lib/structured-data"; import { sanitizeTitle, getSafeDescription } from "../../utils/seo"; -const PostBody = dynamic(() => import("../../components/post-body"), { - ssr: false, -}); +const PostBody = dynamic(() => import("../../components/post-body")); // Apply all HTML transformations in one synchronous pass so the // transformed content is available at render time (SSR-friendly) and diff --git a/pages/technology/[slug].tsx b/pages/technology/[slug].tsx index e196b55b..b0857b19 100644 --- a/pages/technology/[slug].tsx +++ b/pages/technology/[slug].tsx @@ -28,9 +28,7 @@ import { } from "../../lib/structured-data"; import { sanitizeTitle, getSafeDescription } from "../../utils/seo"; -const PostBody = dynamic(() => import("../../components/post-body"), { - ssr: false, -}); +const PostBody = dynamic(() => import("../../components/post-body")); const postBody = ({ content, post }) => { const urlPattern = /https:\/\/keploy\.io\/wp\/author\/[^\/]+\//g; diff --git a/tests/e2e/SeoMeta.spec.ts b/tests/e2e/SeoMeta.spec.ts index 1ff25cbd..690dc7d1 100644 --- a/tests/e2e/SeoMeta.spec.ts +++ b/tests/e2e/SeoMeta.spec.ts @@ -126,6 +126,30 @@ test.describe('SEO and Meta Tags Configuration', () => { expect(body).toContain('https://keploy.io/blog/technology'); }); + test('Post page article body should be in server-rendered HTML before JavaScript runs', async ({ request, baseURL }) => { + // GPTBot / ClaudeBot / PerplexityBot fetch raw HTML without executing JS. + // This test uses Playwright's HTTP client (no browser, no JS) to verify + // that the article prose appears in the initial SSR payload — not only + // inside the __NEXT_DATA__ JSON blob. + const response = await request.get(`${baseURL!}/technology/understanding-api-testing-with-keploy`); + expect(response.status()).toBe(200); + + const html = await response.text(); + + // __NEXT_DATA__ always contains the full post JSON — strip it so we only + // check the server-rendered DOM markup. + const htmlWithoutNextData = html.replace( + /