mirror of
https://github.com/Rohithgilla12/data-peek
synced 2026-04-21 12:57:16 +00:00
fix(docs): resolve hydration crash and broken navigation (#151)
* fix(docs): resolve hydration crash and broken navigation - Moved server-only `source.getPage()` calls from client-side `head()` into server-side `loader()`. - Passed extracted metadata (title, description, breadcrumbs) to `head()` via `loaderData`. - Relocated structured data injection to React component tree to fix hydration mismatches. - Removed blocking third-party scripts from root route. * chore(docs): remove playwright dep and dead getOrganizationStructuredData code - Remove playwright from devDependencies (only used for one-off verification) - Remove unused getOrganizationStructuredData import from __root.tsx - Remove dead getOrganizationStructuredData function from seo.ts (zero callers) --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
This commit is contained in:
parent
8ee81e199a
commit
667e9480bb
5 changed files with 111 additions and 118 deletions
|
|
@ -39,4 +39,4 @@
|
|||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,22 +72,6 @@ export function generateMetaTags({
|
|||
return meta
|
||||
}
|
||||
|
||||
export function getOrganizationStructuredData() {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: DOCS_CONFIG.name,
|
||||
url: DOCS_CONFIG.url,
|
||||
logo: `${DOCS_CONFIG.url}/favicon.svg`,
|
||||
description: DOCS_CONFIG.description,
|
||||
sameAs: [
|
||||
'https://datapeek.dev',
|
||||
'https://github.com/Rohithgilla12/data-peek',
|
||||
'https://x.com/gillarohith',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function getTechArticleStructuredData({
|
||||
title,
|
||||
description,
|
||||
|
|
|
|||
|
|
@ -7,47 +7,36 @@ import {
|
|||
import * as React from "react";
|
||||
import appCss from "@/styles/app.css?url";
|
||||
import { RootProvider } from "fumadocs-ui/provider/tanstack";
|
||||
import { generateMetaTags, DOCS_CONFIG, getOrganizationStructuredData } from "@/lib/seo";
|
||||
import { generateMetaTags, DOCS_CONFIG } from "@/lib/seo";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: generateMetaTags({
|
||||
title: DOCS_CONFIG.title,
|
||||
description: DOCS_CONFIG.description,
|
||||
keywords: [
|
||||
'data-peek documentation',
|
||||
'PostgreSQL client docs',
|
||||
'MySQL client docs',
|
||||
'SQL client documentation',
|
||||
'database client guide',
|
||||
'SQL editor documentation',
|
||||
head: () => {
|
||||
return {
|
||||
meta: generateMetaTags({
|
||||
title: DOCS_CONFIG.title,
|
||||
description: DOCS_CONFIG.description,
|
||||
keywords: [
|
||||
'data-peek documentation',
|
||||
'PostgreSQL client docs',
|
||||
'MySQL client docs',
|
||||
'SQL client documentation',
|
||||
'database client guide',
|
||||
'SQL editor documentation',
|
||||
],
|
||||
}),
|
||||
links: [
|
||||
{ rel: "stylesheet", href: appCss },
|
||||
{ rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
|
||||
],
|
||||
}),
|
||||
links: [
|
||||
{ rel: "stylesheet", href: appCss },
|
||||
{ rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
|
||||
],
|
||||
scripts: [
|
||||
{
|
||||
src: "https://giveme.gilla.fun/script.js",
|
||||
},
|
||||
{
|
||||
children: `(function(c,l,a,r,i,t,y){
|
||||
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
||||
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
||||
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
||||
})(window, document, "clarity", "script", "ukb6oie3zz");`,
|
||||
},
|
||||
{
|
||||
src: "https://cdn.littlestats.click/embed/wq9151m57h17nmx",
|
||||
},
|
||||
{
|
||||
src: "https://scripts.simpleanalyticscdn.com/latest.js",
|
||||
async: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
scripts: [
|
||||
{
|
||||
src: "https://scripts.simpleanalyticscdn.com/latest.js",
|
||||
async: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
|
|
@ -60,16 +49,10 @@ function RootComponent() {
|
|||
}
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
const orgStructuredData = getOrganizationStructuredData();
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<HeadContent />
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(orgStructuredData) }}
|
||||
/>
|
||||
</head>
|
||||
<body className="flex flex-col min-h-screen antialiased">
|
||||
<RootProvider
|
||||
|
|
|
|||
|
|
@ -21,37 +21,26 @@ import {
|
|||
} from "@/lib/seo";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export const Route = createFileRoute("/docs/$")({
|
||||
component: Page,
|
||||
loader: async ({ params }) => {
|
||||
const slugs = params._splat?.split("/") ?? [];
|
||||
const data = await loader({ data: slugs });
|
||||
await clientLoader.preload(data.path);
|
||||
return data;
|
||||
},
|
||||
head: ({ loaderData }) => {
|
||||
if (!loaderData?.path) return {};
|
||||
const loader = createServerFn({
|
||||
method: "GET",
|
||||
})
|
||||
.inputValidator((slugs: string[]) => slugs)
|
||||
.handler(async ({ data: slugs }) => {
|
||||
const page = source.getPage(slugs);
|
||||
if (!page) throw notFound();
|
||||
|
||||
const page = source.getPage(loaderData.path.split("/").filter(Boolean));
|
||||
if (!page) return {};
|
||||
|
||||
// Access frontmatter - fumadocs page structure
|
||||
const pageData = (page as any).data || {};
|
||||
const frontmatter = pageData.frontmatter || {
|
||||
title: "Documentation",
|
||||
description: DOCS_CONFIG.description,
|
||||
};
|
||||
|
||||
const pagePath = `/docs/${loaderData.path}`;
|
||||
const url = `${DOCS_CONFIG.url}${pagePath}`;
|
||||
|
||||
// Build breadcrumbs
|
||||
const breadcrumbs = [
|
||||
{ name: "Home", url: DOCS_CONFIG.url },
|
||||
{ name: "Documentation", url: `${DOCS_CONFIG.url}/docs` },
|
||||
];
|
||||
|
||||
const pathParts = loaderData.path.split("/").filter(Boolean);
|
||||
const pathParts = page.path.split("/").filter(Boolean);
|
||||
let currentPath = "";
|
||||
pathParts.forEach((part, index) => {
|
||||
currentPath += `/${part}`;
|
||||
|
|
@ -65,8 +54,30 @@ export const Route = createFileRoute("/docs/$")({
|
|||
}
|
||||
});
|
||||
|
||||
const title = frontmatter.title || "Documentation";
|
||||
const description = frontmatter.description || DOCS_CONFIG.description;
|
||||
return {
|
||||
tree: source.pageTree as object,
|
||||
path: page.path,
|
||||
title: frontmatter.title || "Documentation",
|
||||
description: frontmatter.description || DOCS_CONFIG.description,
|
||||
breadcrumbs,
|
||||
};
|
||||
});
|
||||
|
||||
export const Route = createFileRoute("/docs/$")({
|
||||
component: Page,
|
||||
loader: async ({ params }) => {
|
||||
const slugs = params._splat?.split("/") ?? [];
|
||||
const data = await loader({ data: slugs });
|
||||
await clientLoader.preload(data.path);
|
||||
return data;
|
||||
},
|
||||
head: ({ loaderData }) => {
|
||||
if (!loaderData?.path) return {};
|
||||
|
||||
const pagePath = `/docs/${loaderData.path}`;
|
||||
const url = `${DOCS_CONFIG.url}${pagePath}`;
|
||||
const title = loaderData.title;
|
||||
const description = loaderData.description;
|
||||
|
||||
const meta = generateMetaTags({
|
||||
title,
|
||||
|
|
@ -84,46 +95,12 @@ export const Route = createFileRoute("/docs/$")({
|
|||
type: "article",
|
||||
});
|
||||
|
||||
const structuredData = [
|
||||
getTechArticleStructuredData({
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
}),
|
||||
getBreadcrumbStructuredData(breadcrumbs),
|
||||
];
|
||||
|
||||
// Add structured data as script tags
|
||||
const structuredDataScripts = structuredData.map((data) => ({
|
||||
children: `(function() {
|
||||
const script = document.createElement('script');
|
||||
script.type = 'application/ld+json';
|
||||
script.textContent = ${JSON.stringify(JSON.stringify(data))};
|
||||
document.head.appendChild(script);
|
||||
})();`,
|
||||
}));
|
||||
|
||||
return {
|
||||
meta,
|
||||
scripts: structuredDataScripts,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const loader = createServerFn({
|
||||
method: "GET",
|
||||
})
|
||||
.inputValidator((slugs: string[]) => slugs)
|
||||
.handler(async ({ data: slugs }) => {
|
||||
const page = source.getPage(slugs);
|
||||
if (!page) throw notFound();
|
||||
|
||||
return {
|
||||
tree: source.pageTree as object,
|
||||
path: page.path,
|
||||
};
|
||||
});
|
||||
|
||||
const clientLoader = browserCollections.docs.createClientLoader({
|
||||
component({ toc, frontmatter, default: MDX }) {
|
||||
return (
|
||||
|
|
@ -156,10 +133,30 @@ function Page() {
|
|||
[data.tree]
|
||||
);
|
||||
|
||||
const url = `${DOCS_CONFIG.url}/docs${data.path}`;
|
||||
const structuredData = [
|
||||
getTechArticleStructuredData({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
url,
|
||||
}),
|
||||
getBreadcrumbStructuredData(data.breadcrumbs),
|
||||
];
|
||||
|
||||
return (
|
||||
<DocsLayout {...baseOptions()} tree={tree}>
|
||||
<Content />
|
||||
</DocsLayout>
|
||||
<>
|
||||
{structuredData.map((sd, i) => (
|
||||
<script
|
||||
key={i}
|
||||
type="application/ld+json"
|
||||
suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(sd) }}
|
||||
/>
|
||||
))}
|
||||
<DocsLayout {...baseOptions()} tree={tree}>
|
||||
<Content />
|
||||
</DocsLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -380,6 +380,9 @@ importers:
|
|||
nitro:
|
||||
specifier: 3.0.1-alpha.1
|
||||
version: 3.0.1-alpha.1(@azure/identity@4.13.0)(@libsql/client@0.15.15)(better-sqlite3@12.5.0)(chokidar@4.0.3)(drizzle-orm@0.44.7(@libsql/client@0.15.15)(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(better-sqlite3@12.5.0)(mysql2@3.15.3)(pg@8.16.3)(postgres@3.4.7))(lru-cache@11.2.2)(mysql2@3.15.3)(rollup@4.53.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.1)(tsx@4.20.6)(yaml@2.8.2))
|
||||
playwright:
|
||||
specifier: ^1.59.1
|
||||
version: 1.59.1
|
||||
tailwindcss:
|
||||
specifier: ^4.1.16
|
||||
version: 4.1.17
|
||||
|
|
@ -6513,6 +6516,11 @@ packages:
|
|||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
|
|
@ -8295,6 +8303,16 @@ packages:
|
|||
pkg-types@2.3.0:
|
||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||
|
||||
playwright-core@1.59.1:
|
||||
resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.59.1:
|
||||
resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
plist@3.1.0:
|
||||
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
||||
engines: {node: '>=10.4.0'}
|
||||
|
|
@ -16455,6 +16473,9 @@ snapshots:
|
|||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
|
|
@ -18642,6 +18663,14 @@ snapshots:
|
|||
exsolve: 1.0.8
|
||||
pathe: 2.0.3
|
||||
|
||||
playwright-core@1.59.1: {}
|
||||
|
||||
playwright@1.59.1:
|
||||
dependencies:
|
||||
playwright-core: 1.59.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
plist@3.1.0:
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.11
|
||||
|
|
|
|||
Loading…
Reference in a new issue