Conversation
… infra, about page - Fix critical SSR bailout: split HomeClient into server-rendered LandingPage with client islands (FAQ accordion, CTA/hero sign-in buttons) so AI crawlers see actual content instead of a blank loading spinner - Add JSON-LD structured data: Organization (Hypercerts Foundation with sameAs), WebSite, SoftwareApplication, and FAQPage schemas - Add robots.ts, sitemap.ts, and public/llms.txt for crawler discovery - Add OG/Twitter Card meta tags, canonical URLs, and page-specific metadata to all public pages (terms, privacy, dsa, about) - Add security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy) via next.config.ts - Create /about page (~800 words) with AT Protocol explainer, Hypercerts Foundation background, and social links - Add About link to both landing and global footers - Add hero description paragraph for AI citability Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe pull request adds security headers to Next.js configuration, introduces multiple new pages with SEO metadata (about, privacy, terms, DSA), creates SEO routes (robots.ts, sitemap.ts), and restructures the landing page by server-rendering core content while refactoring client-side visibility logic. A new static llms.txt file and landing page components (hero sign-in button, FAQ accordion, CTA section) complete the changes. Changes
Sequence DiagramsequenceDiagram
actor User
participant Browser as Client Browser
participant Server as Next.js Server
participant LandingPage as LandingPage<br/>(Server Component)
participant HomeClient as HomeClient<br/>(Client Component)
participant AuthContext as Auth Context
User->>Browser: Load Home Page
Browser->>Server: Request /
Server->>LandingPage: Render LandingPage
LandingPage->>Server: Generate HTML (with hero & sections)
Server->>HomeClient: Initialize HomeClient
Server-->>Browser: Return HTML with SSR landing + client component
Browser->>HomeClient: Mount component
HomeClient->>AuthContext: Call useAuth()
AuthContext-->>HomeClient: Return { isLoading, isAuthenticated, openSignIn }
alt User Not Authenticated
HomeClient->>Browser: useEffect finds .landing-ssr
Browser->>Browser: Set display: block (show landing)
else User Authenticated
HomeClient->>Browser: useEffect finds .landing-ssr
Browser->>Browser: Set display: none (hide landing)
end
User->>Browser: Click sign-in button
Browser->>AuthContext: Trigger openSignIn()
AuthContext-->>Browser: Open auth modal/redirect
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (6)
src/components/landing/sections/ready-cta-button.tsx (1)
5-13: Consider extracting a shared sign-in button component.
ReadyCtaButtonandHeroSignInButtonduplicate behavior/markup; a small shared component would reduce drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/landing/sections/ready-cta-button.tsx` around lines 5 - 13, ReadyCtaButton and HeroSignInButton duplicate the same sign-in markup and behavior; extract a reusable SignInButton component that encapsulates the button, image and onClick behavior, then replace ReadyCtaButton and HeroSignInButton to render this new component. Create a SignInButton functional component that accepts props for className (or wrapperClass and imgClass), image src/alt if needed, and a click handler (defaulting to the useAuth().openSignIn if not provided), and update ReadyCtaButton and HeroSignInButton to import and render SignInButton to remove duplication.src/components/landing/home-client.tsx (2)
38-39: Hiding SSR content during loading negates the perceived performance benefit.While hiding the server-rendered landing page during
isLoadingensures correct behavior, it means users still see the loading spinner first instead of the pre-rendered content. The SSR benefit is preserved for crawlers (no JS execution), but real users experience a flash: spinner → content.Consider showing the SSR content immediately and only hiding it when
isAuthenticatedbecomes true:Proposed fix
useEffect(() => { const landingSsr = document.querySelector(".landing-ssr") as HTMLElement | null; if (!landingSsr) return; - if (isLoading) { - landingSsr.style.display = "none"; - } else if (isAuthenticated) { + if (isAuthenticated) { landingSsr.style.display = "none"; } else { landingSsr.style.display = ""; } }, [isLoading, isAuthenticated]);This lets unauthenticated users see the landing page immediately. For authenticated users, there may be a brief flash of the landing page before the dashboard renders, but that's a smaller UX cost than showing a spinner to everyone.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/landing/home-client.tsx` around lines 38 - 39, The current code hides the server-rendered landing content whenever isLoading is true (landingSsr.style.display = "none"), which causes users to see a spinner instead of the SSR content; instead, change the logic to leave the SSR content visible by default and only hide it when isAuthenticated becomes true (i.e., move the landingSsr.style.display = "none" into the branch that runs when isAuthenticated === true), keeping the spinner logic only for explicit loading states that apply to unauthenticated flows; update any related effect or conditional that references isLoading to avoid hiding landingSsr and ensure landingSsr is hidden/removed only when transitioning an authenticated user to the dashboard.
53-54: Usenext/imageinstead of<img>for the loading screen logo.Per coding guidelines, all images should use
next/imagefor optimization benefits.Proposed fix
+import Image from "next/image"; ... <div className="loading-screen__inner"> - <img + <Image src="/assets/certified_brandmark_black.svg" alt="" className="loading-screen__logo" + width={48} + height={48} + priority /> </div>As per coding guidelines: "Use
next/image(not<img>) for all images".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/landing/home-client.tsx` around lines 53 - 54, The loading screen uses a raw <img> element (src="/assets/certified_brandmark_black.svg") which violates the guideline — replace it with Next.js' Image component: import Image from 'next/image', remove the <img> element inside the HomeClient (or the component rendering the loading screen) and render <Image> pointing to the same source, add an appropriate alt, and supply either explicit width/height or use layout/fill props so Next/Image can optimize the asset.src/app/sitemap.ts (1)
6-15: Consider using fixed dates forlastModifiedon static content.Using
new Date()for the homepage and about page meanslastModifiedchanges on every request/build, which may cause search engines to re-crawl unchanged content unnecessarily. For consistency with the other entries and better cache behavior, consider using fixed dates that you update when the content actually changes.Proposed fix
{ url: "https://certified.app", - lastModified: new Date(), + lastModified: new Date("2026-04-15"), changeFrequency: "weekly", priority: 1, }, { url: "https://certified.app/about", - lastModified: new Date(), + lastModified: new Date("2026-04-15"), changeFrequency: "monthly", priority: 0.8, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/sitemap.ts` around lines 6 - 15, The sitemap entries for "https://certified.app" and "https://certified.app/about" currently use new Date(), causing lastModified to change on every build; replace those dynamic lastModified values with explicit fixed dates (e.g., ISO date strings or Date objects created from a fixed string) in the sitemap array entries for those URLs and update them only when the page content actually changes so crawlers and caches behave consistently.src/components/landing/landing-page.tsx (1)
53-53: Usenext/imageinstead of<img>for the footer logo.Per coding guidelines, all images should use
next/imagefor optimization.Proposed fix
+import Image from "next/image"; ... - <img src="/assets/certified_wordmark_black_green.png" alt="Certified" className="landing-footer__logo-img" /> + <Image src="/assets/certified_wordmark_black_green.png" alt="Certified" className="landing-footer__logo-img" width={120} height={24} />As per coding guidelines: "Use
next/image(not<img>) for all images".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/landing/landing-page.tsx` at line 53, Replace the raw <img> in the LandingPage footer with Next.js' Image component: add "import Image from 'next/image'" to the top of the landing-page.tsx, then swap the <img src="/assets/certified_wordmark_black_green.png" alt="Certified" className="landing-footer__logo-img" /> for an <Image> using the same src, alt and className (or appropriate wrapper), and provide explicit width and height (or use fill with a positioned parent) so Next's optimizer can work; update the LandingPage/footer JSX where the element with className "landing-footer__logo-img" is rendered.src/components/landing/sections/faq-accordion.tsx (1)
30-30: Addaria-hidden="true"to the decorative icon.The Plus icon is decorative since the button text already provides the accessible label. Per coding guidelines, decorative elements should have
aria-hidden="true"to prevent screen readers from announcing redundant or confusing content.♿ Proposed fix
- <Plus className={`landing-faq-icon${isOpen ? " landing-faq-icon--open" : ""}`} /> + <Plus className={`landing-faq-icon${isOpen ? " landing-faq-icon--open" : ""}`} aria-hidden="true" />As per coding guidelines: "aria-hidden='true' on decorative elements".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/landing/sections/faq-accordion.tsx` at line 30, The Plus icon is decorative and should be hidden from assistive tech; update the Plus component usage in the FAQ accordion (the Plus JSX element with className `landing-faq-icon${isOpen ? " landing-faq-icon--open" : ""}`) to include the attribute aria-hidden="true" so screen readers ignore the icon while the button text remains the accessible label.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@next.config.ts`:
- Line 20: The global header setting { key: "X-Frame-Options", value: "DENY" }
will block the OAuth iframe callback used by src/app/oauth/callback/page.tsx;
update next.config.ts to exclude the OAuth callback route (or change to
SAMEORIGIN or use frame-ancestors) so the callback can load in an iframe: modify
the place where security headers are defined (the headers array or function that
returns headers) to conditionally omit or alter the X-Frame-Options header for
the OAuth callback path (refer to the OAuth callback route in
src/app/oauth/callback/page.tsx and the X-Frame-Options header entry) and ensure
other routes keep the stricter header.
In `@public/llms.txt`:
- Line 16: Update the text in public/llms.txt so the description hyphenates
"Open-source" (change "Open source repositories for Certified and related
projects" to "Open-source repositories for Certified and related projects");
locate the GitHub link line containing the string
"[GitHub](https://github.com/hypercerts-org): Open source repositories for
Certified and related projects" and replace it with the hyphenated version to
keep copy consistent.
In `@src/components/landing/hero-signin-button.tsx`:
- Around line 10-11: Replace the raw <img> with Next's Image component: add
"import Image from 'next/image'" at the top of the component file and swap the
<img src="/assets/sign_in_with_certified_black.svg" alt="Sign in with Certified"
className="hero__btn-signin-img" /> with an <Image> element using the same src
and alt, preserving the surrounding <button onClick={openSignIn}
className="hero__btn-signin">; provide explicit width and height (or use fill
with a positioned container) and keep the className "hero__btn-signin-img" on
the Image so styles and the openSignIn handler remain intact.
In `@src/components/landing/sections/ready-cta-button.tsx`:
- Around line 10-11: The sign-in button currently uses a plain <img> which
violates the guideline; import Image from 'next/image' at the top of
ready-cta-button.tsx, replace the <img
src="/assets/sign_in_with_certified_black.svg" ... /> with <Image
src="/assets/sign_in_with_certified_black.svg" alt="Sign in with Certified"
className="hero__btn-signin-img" .../> and supply required size props (width and
height) or use layout/fill as appropriate so Next.js can optimize it; apply the
same change in the nearly identical hero-signin-button.tsx and keep the onClick
handler openSignIn and CSS class names unchanged.
---
Nitpick comments:
In `@src/app/sitemap.ts`:
- Around line 6-15: The sitemap entries for "https://certified.app" and
"https://certified.app/about" currently use new Date(), causing lastModified to
change on every build; replace those dynamic lastModified values with explicit
fixed dates (e.g., ISO date strings or Date objects created from a fixed string)
in the sitemap array entries for those URLs and update them only when the page
content actually changes so crawlers and caches behave consistently.
In `@src/components/landing/home-client.tsx`:
- Around line 38-39: The current code hides the server-rendered landing content
whenever isLoading is true (landingSsr.style.display = "none"), which causes
users to see a spinner instead of the SSR content; instead, change the logic to
leave the SSR content visible by default and only hide it when isAuthenticated
becomes true (i.e., move the landingSsr.style.display = "none" into the branch
that runs when isAuthenticated === true), keeping the spinner logic only for
explicit loading states that apply to unauthenticated flows; update any related
effect or conditional that references isLoading to avoid hiding landingSsr and
ensure landingSsr is hidden/removed only when transitioning an authenticated
user to the dashboard.
- Around line 53-54: The loading screen uses a raw <img> element
(src="/assets/certified_brandmark_black.svg") which violates the guideline —
replace it with Next.js' Image component: import Image from 'next/image', remove
the <img> element inside the HomeClient (or the component rendering the loading
screen) and render <Image> pointing to the same source, add an appropriate alt,
and supply either explicit width/height or use layout/fill props so Next/Image
can optimize the asset.
In `@src/components/landing/landing-page.tsx`:
- Line 53: Replace the raw <img> in the LandingPage footer with Next.js' Image
component: add "import Image from 'next/image'" to the top of the
landing-page.tsx, then swap the <img
src="/assets/certified_wordmark_black_green.png" alt="Certified"
className="landing-footer__logo-img" /> for an <Image> using the same src, alt
and className (or appropriate wrapper), and provide explicit width and height
(or use fill with a positioned parent) so Next's optimizer can work; update the
LandingPage/footer JSX where the element with className
"landing-footer__logo-img" is rendered.
In `@src/components/landing/sections/faq-accordion.tsx`:
- Line 30: The Plus icon is decorative and should be hidden from assistive tech;
update the Plus component usage in the FAQ accordion (the Plus JSX element with
className `landing-faq-icon${isOpen ? " landing-faq-icon--open" : ""}`) to
include the attribute aria-hidden="true" so screen readers ignore the icon while
the button text remains the accessible label.
In `@src/components/landing/sections/ready-cta-button.tsx`:
- Around line 5-13: ReadyCtaButton and HeroSignInButton duplicate the same
sign-in markup and behavior; extract a reusable SignInButton component that
encapsulates the button, image and onClick behavior, then replace ReadyCtaButton
and HeroSignInButton to render this new component. Create a SignInButton
functional component that accepts props for className (or wrapperClass and
imgClass), image src/alt if needed, and a click handler (defaulting to the
useAuth().openSignIn if not provided), and update ReadyCtaButton and
HeroSignInButton to import and render SignInButton to remove duplication.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 13cb0579-35c8-4470-ad88-e64b41adb8fe
📒 Files selected for processing (18)
next.config.tspublic/llms.txtsrc/app/about/page.tsxsrc/app/dsa/page.tsxsrc/app/layout.tsxsrc/app/page.tsxsrc/app/privacy/page.tsxsrc/app/robots.tssrc/app/sitemap.tssrc/app/terms/page.tsxsrc/components/landing/hero-signin-button.tsxsrc/components/landing/home-client.tsxsrc/components/landing/landing-page.tsxsrc/components/landing/sections/faq-accordion.tsxsrc/components/landing/sections/faq-content.tsxsrc/components/landing/sections/ready-cta-button.tsxsrc/components/landing/sections/ready-cta-content.tsxsrc/components/layout/footer.tsx
| headers: [ | ||
| { key: "X-Content-Type-Options", value: "nosniff" }, | ||
| { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, | ||
| { key: "X-Frame-Options", value: "DENY" }, |
There was a problem hiding this comment.
X-Frame-Options: DENY will break the OAuth callback flow.
The OAuth callback page at src/app/oauth/callback/page.tsx (lines 26-32) explicitly checks if it's running inside an iframe (window.parent !== window) and uses postMessage to communicate with the parent window. Setting X-Frame-Options: DENY globally prevents any page from loading in an iframe, which will break this embedded authentication flow.
Consider one of these approaches:
- Exclude the OAuth callback route from the DENY header
- Use
SAMEORIGINinstead ofDENY(allows same-origin iframes) - Migrate to
Content-Security-Policy: frame-ancestorsfor more granular control
Proposed fix: Exclude OAuth callback from X-Frame-Options
async headers() {
return [
{
- source: "/(.*)",
+ source: "/((?!oauth/callback).*)",
headers: [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "X-Frame-Options", value: "DENY" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
],
},
+ {
+ source: "/oauth/callback",
+ headers: [
+ { key: "X-Content-Type-Options", value: "nosniff" },
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
+ { key: "X-Frame-Options", value: "SAMEORIGIN" },
+ {
+ key: "Permissions-Policy",
+ value: "camera=(), microphone=(), geolocation=()",
+ },
+ ],
+ },
];
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@next.config.ts` at line 20, The global header setting { key:
"X-Frame-Options", value: "DENY" } will block the OAuth iframe callback used by
src/app/oauth/callback/page.tsx; update next.config.ts to exclude the OAuth
callback route (or change to SAMEORIGIN or use frame-ancestors) so the callback
can load in an iframe: modify the place where security headers are defined (the
headers array or function that returns headers) to conditionally omit or alter
the X-Frame-Options header for the OAuth callback path (refer to the OAuth
callback route in src/app/oauth/callback/page.tsx and the X-Frame-Options header
entry) and ensure other routes keep the stricter header.
| ## About | ||
|
|
||
| - [Hypercerts Foundation](https://hypercerts.org): Parent organization building open-source protocols for impact funding | ||
| - [GitHub](https://github.com/hypercerts-org): Open source repositories for Certified and related projects |
There was a problem hiding this comment.
Hyphenate “Open-source” in this description.
Minor copy edit for consistency/readability.
Proposed fix
-- [GitHub](https://github.com/hypercerts-org): Open source repositories for Certified and related projects
+- [GitHub](https://github.com/hypercerts-org): Open-source repositories for Certified and related projects📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - [GitHub](https://github.com/hypercerts-org): Open source repositories for Certified and related projects | |
| - [GitHub](https://github.com/hypercerts-org): Open-source repositories for Certified and related projects |
🧰 Tools
🪛 LanguageTool
[grammar] ~16-~16: Use a hyphen to join words.
Context: ...https://github.com/hypercerts-org): Open source repositories for Certified and re...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@public/llms.txt` at line 16, Update the text in public/llms.txt so the
description hyphenates "Open-source" (change "Open source repositories for
Certified and related projects" to "Open-source repositories for Certified and
related projects"); locate the GitHub link line containing the string
"[GitHub](https://github.com/hypercerts-org): Open source repositories for
Certified and related projects" and replace it with the hyphenated version to
keep copy consistent.
| <button className="hero__btn-signin" onClick={openSignIn}> | ||
| <img src="/assets/sign_in_with_certified_black.svg" alt="Sign in with Certified" className="hero__btn-signin-img" /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -type f hero-signin-button.tsxRepository: hypercerts-org/certified-app
Length of output: 242
🏁 Script executed:
fd hero-signin-button.tsxRepository: hypercerts-org/certified-app
Length of output: 117
🏁 Script executed:
cat -n src/components/landing/hero-signin-button.tsxRepository: hypercerts-org/certified-app
Length of output: 596
Replace <img> with next/image in this CTA button.
This violates the image handling requirement for TSX components. The next/image component must be used instead of the HTML <img> tag.
Proposed fix
+import Image from "next/image";
+
import { useAuth } from "@/lib/auth/auth-context";
export default function HeroSignInButton() {
const { openSignIn } = useAuth();
return (
<div className="hero__actions">
- <button className="hero__btn-signin" onClick={openSignIn}>
- <img src="/assets/sign_in_with_certified_black.svg" alt="Sign in with Certified" className="hero__btn-signin-img" />
+ <button type="button" className="hero__btn-signin" onClick={openSignIn}>
+ <Image
+ src="/assets/sign_in_with_certified_black.svg"
+ alt="Sign in with Certified"
+ className="hero__btn-signin-img"
+ width={240}
+ height={56}
+ />
</button>
</div>
);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/landing/hero-signin-button.tsx` around lines 10 - 11, Replace
the raw <img> with Next's Image component: add "import Image from 'next/image'"
at the top of the component file and swap the <img
src="/assets/sign_in_with_certified_black.svg" alt="Sign in with Certified"
className="hero__btn-signin-img" /> with an <Image> element using the same src
and alt, preserving the surrounding <button onClick={openSignIn}
className="hero__btn-signin">; provide explicit width and height (or use fill
with a positioned container) and keep the className "hero__btn-signin-img" on
the Image so styles and the openSignIn handler remain intact.
| <button className="hero__btn-signin" onClick={openSignIn}> | ||
| <img src="/assets/sign_in_with_certified_black.svg" alt="Sign in with Certified" className="hero__btn-signin-img" /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the actual file content
cat -n src/components/landing/sections/ready-cta-button.tsx | head -20Repository: hypercerts-org/certified-app
Length of output: 601
🏁 Script executed:
# Also check the hero-signin-button.tsx for comparison
cat -n src/components/landing/hero-signin-button.tsx | head -20Repository: hypercerts-org/certified-app
Length of output: 596
🏁 Script executed:
# Search for other uses of <img> vs next/image in the codebase to understand the pattern
rg "<img" --type tsx --type jsx -c | head -20Repository: hypercerts-org/certified-app
Length of output: 99
🏁 Script executed:
# Check if next/image is being used anywhere in the landing components
rg "from 'next/image'" src/components/landing/ --type tsx --type jsxRepository: hypercerts-org/certified-app
Length of output: 99
🏁 Script executed:
# Search for next/image usage in the codebase
rg "from 'next/image'|from \"next/image\"" --max-count 10Repository: hypercerts-org/certified-app
Length of output: 375
🏁 Script executed:
# Search for <img tags to see how widespread the issue is
rg "<img" --max-count 20 -A 1Repository: hypercerts-org/certified-app
Length of output: 4118
Use next/image instead of <img> for the sign-in button image.
Per coding guidelines: "Use next/image (not <img>) for all images". This also affects the nearly identical hero-signin-button.tsx component.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/landing/sections/ready-cta-button.tsx` around lines 10 - 11,
The sign-in button currently uses a plain <img> which violates the guideline;
import Image from 'next/image' at the top of ready-cta-button.tsx, replace the
<img src="/assets/sign_in_with_certified_black.svg" ... /> with <Image
src="/assets/sign_in_with_certified_black.svg" alt="Sign in with Certified"
className="hero__btn-signin-img" .../> and supply required size props (width and
height) or use layout/fill as appropriate so Next.js can optimize it; apply the
same change in the nearly identical hero-signin-button.tsx and keep the onClick
handler openSignIn and CSS class names unchanged.
Summary
HomeClientinto server-renderedLandingPagewith client islands so AI crawlers see actual content instead of a blank loading spinnerrobots.ts,sitemap.ts,public/llms.txt/aboutpage (~800 words) covering what Certified is, AT Protocol, Hypercerts Foundation, and contact infoAddresses findings from the GEO audit (composite score: 20/100). No visible changes to existing pages except the About footer link.
Test plan
/— verify HTML contains landing content (not just a loading spinner)/about— verify content renders correctly/robots.txt— verify it returns valid robots.txt with sitemap reference/sitemap.xml— verify it lists all public pages/llms.txt— verify it returns the llms.txt file/terms— verify About link appears before Terms/(not the landing page)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation & SEO
Security
Refactor