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(
+ /