From 554f39e0a36dc0308a22236737fbc5833fea1d16 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Tue, 29 Sep 2026 01:36:13 -0500 Subject: [PATCH 1/2] feat(seo): add product pages, starting with QuickBooks MCP A product page answers a query whose search results are the product itself rather than articles. One registry drives each page's metadata, structured data, sitemap entry, llms.txt line and footer link, and a test checks every entry against the SEO checklist (query-led title, description under 160 characters, query in the H1, self canonical, real lastmod, a docs link, a route and social card). --- src/app/(pages)/layout.tsx | 18 ++ .../quickbooks-mcp/opengraph-image.tsx | 16 ++ src/app/(pages)/quickbooks-mcp/page.tsx | 242 ++++++++++++++++++ .../(pages)/quickbooks-mcp/twitter-image.tsx | 1 + src/app/__tests__/sitemap.test.ts | 13 + src/app/llms.txt/__tests__/route.test.ts | 3 + src/app/llms.txt/route.ts | 6 + src/app/sitemap.ts | 9 +- src/components/landing/Footer.tsx | 5 + src/lib/__tests__/product-pages.test.ts | 65 +++++ src/lib/product-pages.ts | 106 ++++++++ 11 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 src/app/(pages)/layout.tsx create mode 100644 src/app/(pages)/quickbooks-mcp/opengraph-image.tsx create mode 100644 src/app/(pages)/quickbooks-mcp/page.tsx create mode 100644 src/app/(pages)/quickbooks-mcp/twitter-image.tsx create mode 100644 src/lib/__tests__/product-pages.test.ts create mode 100644 src/lib/product-pages.ts diff --git a/src/app/(pages)/layout.tsx b/src/app/(pages)/layout.tsx new file mode 100644 index 00000000..89821f1e --- /dev/null +++ b/src/app/(pages)/layout.tsx @@ -0,0 +1,18 @@ +import Footer from '@/components/landing/Footer' +import Header from '@/components/landing/Header' + +// Product pages wear the marketing header and footer, like the blog. They render on the +// server with no auth gate in front, so a crawler gets the whole page in the first response. +export default function ProductPagesLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( +
+
+
{children}
+
+
+ ) +} diff --git a/src/app/(pages)/quickbooks-mcp/opengraph-image.tsx b/src/app/(pages)/quickbooks-mcp/opengraph-image.tsx new file mode 100644 index 00000000..cec4e248 --- /dev/null +++ b/src/app/(pages)/quickbooks-mcp/opengraph-image.tsx @@ -0,0 +1,16 @@ +import { OG_CONTENT_TYPE, OG_SIZE, renderOgImage } from '@/lib/og' +import { getProductPage } from '@/lib/product-pages' + +const page = getProductPage('/quickbooks-mcp') + +export const size = OG_SIZE +export const contentType = OG_CONTENT_TYPE +export const alt = page.title + +export default function Image() { + return renderOgImage({ + eyebrow: page.ogEyebrow, + title: page.h1, + subtitle: page.ogSubtitle, + }) +} diff --git a/src/app/(pages)/quickbooks-mcp/page.tsx b/src/app/(pages)/quickbooks-mcp/page.tsx new file mode 100644 index 00000000..5ca55109 --- /dev/null +++ b/src/app/(pages)/quickbooks-mcp/page.tsx @@ -0,0 +1,242 @@ +import FinalCTA from '@/components/landing/FinalCTA' +import { REGISTER_PATH } from '@/components/landing/constants' +import { + getProductPage, + productPageJsonLd, + productPageMetadata, +} from '@/lib/product-pages' +import Link from 'next/link' +import type { ReactNode } from 'react' + +// Every claim here is checked against docs/product/roboledger (connect-your-books, +// what-claude-can-do, quickbooks-write-back) on the day it ships. + +const page = getProductPage('/quickbooks-mcp') + +export const metadata = productPageMetadata(page) + +const MCP_URL = 'https://api.robosystems.ai/v1/mcp/roboledger' + +const clients: { name: string; how: ReactNode; code?: string }[] = [ + { + name: 'Claude (claude.ai and Claude Desktop)', + how: 'Customize → Connectors → Add → Add custom connector, and paste the address.', + }, + { + name: 'Claude Code', + how: 'Run this, then /mcp to sign in.', + code: `claude mcp add --transport http roboledger ${MCP_URL}`, + }, + { + name: 'ChatGPT', + how: 'Turn on developer mode, then Settings → Connectors → Create, and paste the address.', + }, + { + name: 'Cursor, VS Code and other MCP clients', + how: "Add the address to the client's MCP configuration. The client runs the sign-in for you.", + code: `"roboledger": { "url": "${MCP_URL}" }`, + }, +] + +const capabilities = [ + { + title: 'Analyze', + body: 'Ask why gross margin dropped or which vendors grew fastest. It reads your statements, trial balance, journal entries, customers and vendors, as of your last sync. Reading uses no credits.', + }, + { + title: 'Report', + body: 'Build the balance sheet, income statement, cash flow and statement of equity for a period, then download them as XBRL or share them with another graph.', + }, + { + title: 'Plan', + body: 'Project scenarios like hiring or a new contract month by month from your actuals. A forecast is calculated, not generated.', + }, + { + title: 'Compare', + body: 'Add the SEC filings graph as a second connection and put your margins and growth next to public companies in your industry. It is a separate subscription.', + }, + { + title: 'Close', + body: 'When you trust the numbers, the month-end entries arrive drafted for review, and you see what will be written to QuickBooks before anything posts.', + }, +] + +// `wrap` breaks a lone URL across lines on a phone rather than hiding its end. +function Code({ children, wrap }: { children: string; wrap?: boolean }) { + return ( +
+      {children}
+    
+ ) +} + +function Section({ title, children }: { title: string; children: ReactNode }) { + return ( +
+

+ {title} +

+
+ {children} +
+
+ ) +} + +export default function QuickBooksMcpPage() { + return ( + <> +