-
Notifications
You must be signed in to change notification settings - Fork 1
feat: enhance SEO with dynamic OG images, improved metadata, and sitemap #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add dynamic Open Graph image generation using Satori API endpoint - Implement comprehensive SEO metadata with structured data (JSON-LD) - Add robots.txt and sitemap.xml generation for better search indexing - Include custom 404 page for improved user experience - Update Astro configuration for server-side rendering - Add proper meta tags including Open Graph, Twitter cards, and schema markup 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
|
|
Caution Review failedThe pull request is closed. WalkthroughAdds sitemap integration and server output to Astro; centralizes and updates site branding; migrates pages to a metadata-driven RootLayout; adds dynamic OG PNG generation and robots.txt routes; updates manifest and dependencies; adds removeTrailingSlash utility and small UI/text and layout tweaks. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User Agent
participant OG as /og (Astro Route)
participant SC as siteConfig
participant FS as Public Fonts
participant SA as Satori (WASM)
participant RV as Resvg (WASM)
U->>OG: GET /og?title=&description=
OG->>SC: read defaults (title, description)
OG->>FS: load fonts (cached)
OG->>SA: render SVG (title, description)
SA-->>OG: SVG
OG->>RV: rasterize SVG → PNG (1200×630)
RV-->>OG: PNG buffer
OG-->>U: 200 image/png (+cache headers)
note right of OG: prerender = false
sequenceDiagram
autonumber
participant B as Browser
participant P as Astro Page
participant SC as siteConfig
participant RL as RootLayout
B->>P: Request page
P->>SC: read site defaults (title, description, images)
P->>RL: render with metadata {title, description, openGraph, twitter, url}
RL-->>B: HTML with head meta (OG, Twitter, canonical, robots, theme-color) + inline theme script
sequenceDiagram
autonumber
participant Bot as Crawler
participant R as /robots.txt (Astro Route)
participant S as site (Astro site URL)
Bot->>R: GET /robots.txt
R->>S: resolve("sitemap-index.xml")
R-->>Bot: robots.txt (User-agent: *, Allow: /, Sitemap: <url>)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/www/package.json (1)
32-43: Use resvg at runtime; avoid sharp on Workers.Great additions for Satori/resvg. Please also replace sharp usage in the OG route for Cloudflare SSR (Workers) compatibility, or feature-detect Node to use sharp only locally.
If keeping sharp for local dev only, guard import:
const isNode = typeof process !== "undefined" && !!process.versions?.nodeThen prefer Resvg path on Workers.
apps/www/src/components/landing/cta.astro (1)
31-31: Add noopener for external link security.
target="_blank"should includerel="noopener noreferrer".Apply:
- <a href={siteConfig.links.github} target="_blank" rel="noreferrer"> + <a href={siteConfig.links.github} target="_blank" rel="noopener noreferrer">apps/www/src/pages/examples/[...slug].astro (1)
7-13: Replaceentry.idwithentry.slugand remove the unnecessary.flat(1)
getCollection()returns a flat array of entries; for content collectionsentry.idincludes file extensions, whereasentry.slugis URL-ready and omits extensions (v4.docs.astro.build). Use:- return collections.flat(1).map((entry) => ({ - params: { slug: entry.id }, + return collections.map((entry) => ({ + params: { slug: entry.slug }, props: { entry }, }))apps/www/src/pages/docs/[...slug].astro (1)
11-17: Use entry.slug for route params instead of entry.id- return overview.map((entry) => ({ - params: { slug: entry.id }, + return overview.map((entry) => ({ + params: { slug: entry.slug }, props: { entry }, }))
🧹 Nitpick comments (24)
apps/www/package.json (1)
32-43: Fix pipeline warning: add a license field.CI reports “No license field in package.json”. Since this package is private, pick UNLICENSED or match the repo’s license.
Apply:
"private": true, + "license": "UNLICENSED", "type": "module",apps/www/astro.config.ts (1)
20-21: LGTM: enabling SSR and setting canonical site.output: "server" with the Cloudflare adapter and site set to the production URL is correct and unblocks robots/sitemap absolute URLs.
Confirm OG generation path avoids Node-only deps (sharp) to keep SSR deployable on Workers.
apps/www/src/pages/sitemap.xml.ts (3)
11-23: Escape XML text to avoid invalid documents.If any URL or field ever contains special chars, the XML becomes invalid. Minimal escaper keeps this robust.
Apply:
+const escapeXml = (s: string) => + s + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + const xml = ( urls: { loc: string lastmod?: string changefreq?: string priority?: number }[] ) => `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ${urls .map( - (u) => ` <url> - <loc>${u.loc}</loc> - ${u.lastmod ? `<lastmod>${u.lastmod}</lastmod>` : ""} - ${u.changefreq ? `<changefreq>${u.changefreq}</changefreq>` : ""} - ${typeof u.priority === "number" ? `<priority>${u.priority.toFixed(1)}</priority>` : ""} - </url>` + (u) => ` <url> + <loc>${escapeXml(u.loc)}</loc> + ${u.lastmod ? `<lastmod>${escapeXml(u.lastmod)}</lastmod>` : ""} + ${u.changefreq ? `<changefreq>${escapeXml(u.changefreq)}</changefreq>` : ""} + ${ + typeof u.priority === "number" + ? `<priority>${u.priority.toFixed(1)}</priority>` + : "" + } + </url>` ) .join("\n")} </urlset>`
44-49: Spec polish: avoid hardcoding daily/weekly; optionally tune per route.Optional: compute changefreq/priority from content metadata when available.
51-56: CDN cache hint.Consider s-maxage + stale-while-revalidate to improve cache behavior on proxies/CDN.
Apply:
- "Cache-Control": "public, max-age=3600", + "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",apps/www/src/config/site.ts (2)
10-10: Store a Twitter handle separately for cleaner meta tags.Consider
twitterHandle: "@shipba_se"and keeplinks.twitterfor the profile URL; use the handle fortwitter:creator.
1-1: Constant naming guideline (nit).Guidelines say constants use SCREAMING_SNAKE_CASE;
siteConfigis widely used, so only change if tooling enforces it.apps/www/src/components/landing/cta.astro (1)
21-23: Heading copy polish (brand + style).If you follow shadcn’s styling, prefer “shadcn/ui”. Also consider “Multi‑framework”.
Apply:
- Shadcn UI Components for<br class="md:hidden" /> + shadcn/ui components for<br class="md:hidden" /> - <span class="hidden md:inline"> </span>Multi Framework + <span class="hidden md:inline"> </span>Multi‑frameworkapps/www/src/pages/docs/components/[component].astro (1)
31-39: Provide a safe fallback for missing frontmatter.If a doc lacks
description, fall back tositeConfig.description.Apply:
+--- // add import +import { siteConfig } from "@/config/site" +--- -<RootLayout title={entry.data.title} description={entry.data.description}> +<RootLayout + title={entry.data.title} + description={entry.data.description ?? siteConfig.description} +>apps/www/src/pages/404.astro (1)
5-11: 404 page: consider prerender and explicit noindex.Astro serves 404 correctly, but prerendering helps static deployments; ensure RootLayout emits
noindexon 404s.Apply:
--- import RootLayout from "@/layouts/root-layout.astro" --- +--- +export const prerender = true +--- + <RootLayout title="Page Not Found">apps/www/src/pages/index.astro (1)
17-17: Title nit: trailing space and hyphenation.Trim and use “Multi‑framework”.
Apply:
-<RootLayout title="Shadcn UI Components for Multi Framework "> +<RootLayout title="shadcn/ui components for Multi‑framework">apps/www/src/pages/examples/[...slug].astro (2)
14-14: Type-safety for Astro.props (avoid silent runtime shape drift).Annotate the expected props to ensure
entryis present and correctly typed.-const { entry } = Astro.props +import type { CollectionEntry } from "astro:content" +const { entry } = Astro.props as { entry: CollectionEntry<"components"> }
17-17: Guard against missing description to avoid "undefined" in meta tags.Fallback to site defaults when
entry.data.descriptionis absent.+import { siteConfig } from "@/config/site" -<RootLayout title={`${entry.data.title} Examples`} description={entry.data.description}> +<RootLayout + title={`${entry.data.title} Examples`} + description={entry.data.description ?? siteConfig.description} +>apps/www/src/pages/docs/[...slug].astro (1)
23-31: Add safe fallbacks for metadata (prevents undefined in head tags).Use site defaults when frontmatter is missing.
+import { siteConfig } from "@/config/site" -<RootLayout title={entry.data.title} description={entry.data.description}> +<RootLayout + title={entry.data.title ?? siteConfig.name} + description={entry.data.description ?? siteConfig.description} +>apps/www/src/pages/og/index.ts (6)
44-53: Also cache the logo data URL to avoid repeated reads/base64 conversions.-async function loadLogo(dark = true) { +let logoCacheDark: string | null = null +let logoCacheLight: string | null = null +async function loadLogo(dark = true) { const publicPath = path.join(process.cwd(), "public") const fileName = dark ? "logo-dark.png" : "logo.png" const filePath = path.join(publicPath, fileName) const buffer = await readFile(filePath) const mime = "image/png" const dataUrl = `data:${mime};base64,${buffer.toString("base64")}` - return dataUrl + if (dark) logoCacheDark = logoCacheDark ?? dataUrl + else logoCacheLight = logoCacheLight ?? dataUrl + return dark ? logoCacheDark! : logoCacheLight! }
1-7: Cloudflare compatibility claim vs. Node-only APIs (fs/sharp).
node:fs/promisesandsharpwon’t run on Workers/edge. If you need Worker/edge compatibility, switch toresvg-wasmand import assets vianew URL(..., import.meta.url).Edge-compatible sketch (for later):
// import { Resvg } from "@resvg/resvg-wasm" // const svg = await satori(tree, { fonts }) // const png = new Resvg(svg, { fitTo: { mode: "width", value: 1200 } }).render().asPng()
299-305: Strengthen cache headers for CDN friendliness.Add
publicand consider higher TTLs; OG images aren’t highly dynamic.- "Cache-Control": "s-maxage=1, stale-while-revalidate=59", + "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
14-21: File path resolution is brittle withprocess.cwd(); prefer URL-based imports.More robust across adapters/builds.
// Example: import { fileURLToPath } from "node:url" const fontsDir = fileURLToPath(new URL("../../../../public/fonts", import.meta.url))
58-60: Input length sanity limits (defense-in-depth).Consider clamping
title/descriptionlength to avoid pathological inputs.const clamp = (s: string, n: number) => (s.length > n ? s.slice(0, n) + "…" : s) const title = clamp(searchParams.get("title") ?? siteConfig.name, 140) const description = clamp(searchParams.get("description") ?? siteConfig.description, 240)
61-63: Minor: avoid duplicate I/O by reusing cached logo.Use the new cache helper.
- const fonts = await loadFonts() - const logoSrc = await loadLogo(true) + const fonts = await getFonts() + const logoSrc = await loadLogo(true)apps/www/src/layouts/root-layout.astro (4)
59-66: Add twitter:site for brand attribution.Expose both creator and site when available.
const twitterUrl: string | undefined = siteConfig.links.twitter const twitterCreator = (() => { if (!twitterUrl) return undefined const match = twitterUrl.match( /^https?:\/\/(?:x\.com|twitter\.com)\/([^\/?#]+)/ ) return match?.[1] ? (`@${match[1]}` as const) : undefined })() +const twitterSite = siteConfig.twitterSite ?? twitterCreator<meta name="twitter:image" content={twitterImageAbs} /> <meta name="twitter:image:alt" content={ogImageAlt} /> {twitterCreator ? <meta name="twitter:creator" content={twitterCreator} /> : null} +{twitterSite ? <meta name="twitter:site" content={twitterSite} /> : null}Also applies to: 142-147
135-141: Emit og:image:secure_url when using HTTPS.Small boost for some scrapers.
<meta property="og:image" content={ogImageAbs} /> <meta property="og:image:alt" content={ogImageAlt} /> {ogImageType ? <meta property="og:image:type" content={ogImageType} /> : null} +{ogImageAbs.startsWith("https") ? ( + <meta property="og:image:secure_url" content={ogImageAbs} /> +) : null} <meta property="og:image:width" content={String(ogImageWidth)} /> <meta property="og:image:height" content={String(ogImageHeight)} />
119-119: Prefer media-query theme-color metas; script can be optional.This avoids JS in head and shows correct color on first paint.
-<meta name="theme-color" content="#ffffff" /> +<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" /> +<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#09090b" />You can keep the script as a progressive enhancement if honoring a stored “system” mode is required.
49-57: Drop keywords meta; modern search engines ignore it.Saves bytes and maintenance.
-const keywords = [ - "Ark UI", - "Tailwind CSS", - "React", - "Vue", - "UI Components", - "Component Library", -] - .filter(Boolean) - .join(", ")-<meta name="keywords" content={keywords} />Also applies to: 116-116
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (8)
apps/www/public/fonts/Geist-Regular.ttfis excluded by!**/*.ttfapps/www/public/fonts/Geist-SemiBold.ttfis excluded by!**/*.ttfapps/www/public/fonts/GeistMono-Regular.ttfis excluded by!**/*.ttfapps/www/public/logo-dark.pngis excluded by!**/*.pngapps/www/public/logo-dark.svgis excluded by!**/*.svgapps/www/public/logo.pngis excluded by!**/*.pngapps/www/public/og-image.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
apps/www/astro.config.ts(1 hunks)apps/www/package.json(2 hunks)apps/www/src/components/landing/cta.astro(1 hunks)apps/www/src/config/site.ts(1 hunks)apps/www/src/layouts/root-layout.astro(2 hunks)apps/www/src/pages/404.astro(1 hunks)apps/www/src/pages/docs/[...slug].astro(2 hunks)apps/www/src/pages/docs/components/[component].astro(2 hunks)apps/www/src/pages/examples/[...slug].astro(1 hunks)apps/www/src/pages/index.astro(1 hunks)apps/www/src/pages/og/index.ts(1 hunks)apps/www/src/pages/robots.txt.ts(1 hunks)apps/www/src/pages/sitemap.xml.ts(1 hunks)pnpm-workspace.yaml(1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
{package.json,pnpm-workspace.yaml,turbo.json,biome.jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Ensure all configuration files are kept up-to-date and valid (package.json, pnpm-workspace.yaml, turbo.json, biome.jsonc)
Files:
pnpm-workspace.yaml
pnpm-workspace.yaml
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The pnpm-workspace.yaml file must be present at the root for pnpm workspace configuration and dependency catalog
Files:
pnpm-workspace.yaml
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
apps/www/src/pages/robots.txt.tsapps/www/astro.config.tsapps/www/src/pages/og/index.tsapps/www/src/pages/sitemap.xml.tsapps/www/src/config/site.ts
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
apps/www/src/pages/robots.txt.tsapps/www/astro.config.tsapps/www/package.jsonapps/www/src/pages/og/index.tsapps/www/src/pages/sitemap.xml.tsapps/www/src/config/site.ts
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/src/pages/robots.txt.tsapps/www/astro.config.tsapps/www/src/pages/404.astroapps/www/package.jsonapps/www/src/pages/docs/[...slug].astroapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astroapps/www/src/components/landing/cta.astroapps/www/src/pages/index.astroapps/www/src/pages/examples/[...slug].astroapps/www/src/pages/og/index.tsapps/www/src/pages/sitemap.xml.tsapps/www/src/config/site.ts
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/pages/robots.txt.tsapps/www/src/pages/404.astroapps/www/src/pages/docs/[...slug].astroapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astroapps/www/src/components/landing/cta.astroapps/www/src/pages/index.astroapps/www/src/pages/examples/[...slug].astroapps/www/src/pages/og/index.tsapps/www/src/pages/sitemap.xml.tsapps/www/src/config/site.ts
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/src/pages/robots.txt.tsapps/www/astro.config.tsapps/www/src/pages/404.astroapps/www/package.jsonapps/www/src/pages/docs/[...slug].astroapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astroapps/www/src/components/landing/cta.astroapps/www/src/pages/index.astroapps/www/src/pages/examples/[...slug].astroapps/www/src/pages/og/index.tsapps/www/src/pages/sitemap.xml.tsapps/www/src/config/site.ts
apps/www/astro.config.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Files:
apps/www/astro.config.ts
🧠 Learnings (9)
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: Applies to pnpm-workspace.yaml : The pnpm-workspace.yaml file must be present at the root for pnpm workspace configuration and dependency catalog
Applied to files:
pnpm-workspace.yaml
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: Applies to apps/www/astro.config.ts : Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Applied to files:
apps/www/src/pages/robots.txt.tsapps/www/astro.config.tsapps/www/src/pages/docs/[...slug].astroapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astroapps/www/src/pages/examples/[...slug].astroapps/www/src/pages/sitemap.xml.tsapps/www/src/config/site.ts
📚 Learning: 2025-08-03T17:49:50.919Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/overview.mdc:0-0
Timestamp: 2025-08-03T17:49:50.919Z
Learning: Applies to packages/{react,vue}/src/components/ui/**/* : Use Tailwind CSS and CVA for component customization and styling
Applied to files:
apps/www/package.json
📚 Learning: 2025-08-03T17:49:32.389Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/component-development.mdc:0-0
Timestamp: 2025-08-03T17:49:32.389Z
Learning: Applies to {packages/react/src/components/ui/*.tsx,packages/vue/src/components/ui/*/*.vue} : Use Tailwind CSS for styling
Applied to files:
apps/www/package.json
📚 Learning: 2025-08-03T17:49:32.389Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/component-development.mdc:0-0
Timestamp: 2025-08-03T17:49:32.389Z
Learning: Applies to packages/react/src/components/ui/*.tsx : React components are built on top of Ark UI components
Applied to files:
apps/www/src/components/landing/cta.astro
📚 Learning: 2025-08-03T17:49:16.881Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/code-standards.mdc:0-0
Timestamp: 2025-08-03T17:49:16.881Z
Learning: Applies to **/*.{tsx,vue} : Use Ark UI components as foundation for accessibility
Applied to files:
apps/www/src/components/landing/cta.astro
📚 Learning: 2025-08-30T18:56:37.501Z
Learnt from: CR
PR: shipbase/ui#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T18:56:37.501Z
Learning: Build components on top of Ark UI primitives
Applied to files:
apps/www/src/components/landing/cta.astro
📚 Learning: 2025-08-03T17:49:32.389Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/component-development.mdc:0-0
Timestamp: 2025-08-03T17:49:32.389Z
Learning: Applies to packages/react/src/components/ui/*.tsx : Define component variants through CVA
Applied to files:
apps/www/src/components/landing/cta.astro
📚 Learning: 2025-08-03T17:49:50.919Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/overview.mdc:0-0
Timestamp: 2025-08-03T17:49:50.919Z
Learning: Applies to packages/{react,vue}/src/components/ui/**/* : Ensure all UI components are accessible, leveraging Ark UI for accessibility features
Applied to files:
apps/www/src/components/landing/cta.astro
🧬 Code graph analysis (7)
apps/www/src/pages/robots.txt.ts (1)
apps/www/src/pages/sitemap.xml.ts (1)
GET(25-57)
apps/www/src/pages/404.astro (1)
examples/next/app/layout.tsx (1)
RootLayout(20-34)
apps/www/src/pages/docs/components/[component].astro (2)
examples/next/app/layout.tsx (1)
RootLayout(20-34)apps/www/src/lib/docs.ts (1)
components(3-12)
apps/www/src/pages/index.astro (1)
examples/next/app/layout.tsx (1)
RootLayout(20-34)
apps/www/src/pages/examples/[...slug].astro (1)
examples/next/app/layout.tsx (1)
RootLayout(20-34)
apps/www/src/pages/og/index.ts (1)
apps/www/src/config/site.ts (1)
siteConfig(1-13)
apps/www/src/pages/sitemap.xml.ts (2)
apps/www/src/pages/og/index.ts (1)
GET(55-306)apps/www/src/pages/robots.txt.ts (1)
GET(3-15)
🪛 GitHub Actions: Code check
apps/www/package.json
[warning] 1-1: No license field in package.json.
🔇 Additional comments (10)
apps/www/src/pages/sitemap.xml.ts (1)
29-42: Content collections verifiedapps/www/src/config/site.ts (2)
3-3: Ensure Astro’ssiteand URL generation use the new domain
– In apps/www/astro.config.ts, import theurlfrom apps/www/src/config/site.ts and setsite: urltohttps://ui.shipbase.xyz.
– In apps/www/src/pages/sitemap.xml.ts and robots.txt.ts, switch URL construction to use Astro.site (or yoursiteConfig.url) to guarantee the same origin and apply a uniform trailing-slash policy.
4-6: OG endpoint and meta setup verifiedThe
/api/ogroute returns a 200 response withContent-Type: image/pngandCache-Control: s-maxage=1, stale-while-revalidate=59. Inroot-layout.astro, bothog:imageandtwitter:imageare built as absolute URLs viaAstro.site(e.g.${baseUrl}/api/og), and the corresponding<meta>tags use these values. No further changes needed.apps/www/src/components/landing/cta.astro (1)
25-25: Good: single source of truth for description.apps/www/src/pages/docs/components/[component].astro (1)
9-10: Prerendering component docs — LGTM.Static output improves crawlability and page speed.
apps/www/src/pages/index.astro (1)
20-27: Self-closing separators — LGTM.apps/www/src/pages/docs/[...slug].astro (1)
9-9: Good call enabling prerender for docs.Improves TTFB and resiliency for documentation pages.
apps/www/src/pages/og/index.ts (3)
95-96: Font fallback list is fine.Good use of a system fallback chain behind Geist.
210-284: Layout composition looks solid and resilient to long titles.Good choices on
wordBreak,whiteSpace, and dynamicfontSize.
299-299: PNG generation path is correct.
satori -> svg -> sharp.png()is a reliable Node pipeline.
| const siteName = siteConfig.name | ||
| const baseUrl = Astro.site?.toString().replace(/\/$/, "") ?? "" | ||
| const path = Astro.url?.pathname ?? "/" | ||
| const canonical = `${baseUrl}${path}` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Ensure absolute canonicals/LD-JSON by hardening baseUrl fallback.
If Astro.site isn’t set, you’ll emit relative URLs. Prefer siteConfig.url, then Astro.site, then Astro.url.origin.
-const baseUrl = Astro.site?.toString().replace(/\/$/, "") ?? ""
+const baseUrl =
+ siteConfig.url?.replace(/\/$/, "") ||
+ Astro.site?.toString().replace(/\/$/, "") ||
+ Astro.url?.origin?.replace(/\/$/, "") ||
+ ""📝 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.
| const siteName = siteConfig.name | |
| const baseUrl = Astro.site?.toString().replace(/\/$/, "") ?? "" | |
| const path = Astro.url?.pathname ?? "/" | |
| const canonical = `${baseUrl}${path}` | |
| const siteName = siteConfig.name | |
| const baseUrl = | |
| siteConfig.url?.replace(/\/$/, "") || | |
| Astro.site?.toString().replace(/\/$/, "") || | |
| Astro.url?.origin?.replace(/\/$/, "") || | |
| "" | |
| const path = Astro.url?.pathname ?? "/" | |
| const canonical = `${baseUrl}${path}` |
🤖 Prompt for AI Agents
In apps/www/src/layouts/root-layout.astro around lines 21 to 25, the baseUrl
fallback can produce relative URLs when Astro.site is unset; change the logic to
prefer siteConfig.url first, then Astro.site, then Astro.url.origin, and
normalize by stripping any trailing slash before composing canonical and LD-JSON
values so they are absolute; implement that fallback order and trimming so
canonical uses `${baseUrl}${path}` with baseUrl guaranteed to be an origin when
available.
| const ogDynamic = `/og?title=${encodeURIComponent(title ?? siteName)}&description=${encodeURIComponent(pageDescription)}` | ||
| const ogImage = image ?? (siteConfig.ogImage || ogDynamic) | ||
| const ogImageAbs = ogImage.startsWith("http") ? ogImage : `${baseUrl}${ogImage}` | ||
| const twitterImage = siteConfig.twitterImage || ogImage | ||
| const twitterImageAbs = twitterImage.startsWith("http") | ||
| ? twitterImage | ||
| : `${baseUrl}${twitterImage}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix OG generator path and centralize the endpoint.
PR and tests reference /api/og, but ogDynamic defaults to /og, which will 404 and break previews. Also, reuse a single ogEndpoint for type inference.
Apply:
-// Use dynamic OG generator by default
-const ogDynamic = `/og?title=${encodeURIComponent(title ?? siteName)}&description=${encodeURIComponent(pageDescription)}`
-const ogImage = image ?? (siteConfig.ogImage || ogDynamic)
+// Use dynamic OG generator by default
+const ogEndpoint = siteConfig.ogEndpoint ?? "/api/og"
+const ogDynamic = `${ogEndpoint}?title=${encodeURIComponent(title ?? siteName)}&description=${encodeURIComponent(pageDescription)}`
+const ogImage = image ?? (siteConfig.ogImage || ogDynamic)-const isLikelyPng =
- /\.png($|\?)/.test(ogImageAbs) ||
- ogImageAbs.includes("/api/og") ||
- ogImageAbs.includes("/og")
+const isLikelyPng =
+ /\.png($|\?)/.test(ogImageAbs) ||
+ ogImageAbs.includes(ogEndpoint)Also applies to: 36-40
🤖 Prompt for AI Agents
In apps/www/src/layouts/root-layout.astro around lines 29 to 40, the dynamic OG
generator is using the wrong path and duplicates logic; change the default `/og`
to the correct `/api/og` and introduce a single const ogEndpoint = "/api/og" (or
derive from config) to centralize the endpoint, then use that ogEndpoint when
building ogDynamic, ogImage (fallback), and twitterImage (fallback) so both
images and absolute URL logic reuse the same endpoint and type inference; ensure
you keep the encodeURIComponent calls and compute the absolute URLs from baseUrl
only once using the shared values.
| const ogImageAbs = ogImage.startsWith("http") ? ogImage : `${baseUrl}${ogImage}` | ||
| const twitterImage = siteConfig.twitterImage || ogImage | ||
| const twitterImageAbs = twitterImage.startsWith("http") | ||
| ? twitterImage | ||
| : `${baseUrl}${twitterImage}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make URL resolution robust (handles no leading slash and relative paths).
String concatenation can yield malformed URLs (e.g., https://site.comimages/og.png). Use URL() with a fallback base.
-const ogImageAbs = ogImage.startsWith("http") ? ogImage : `${baseUrl}${ogImage}`
-const twitterImage = siteConfig.twitterImage || ogImage
-const twitterImageAbs = twitterImage.startsWith("http")
- ? twitterImage
- : `${baseUrl}${twitterImage}`
+const toAbs = (u: string) => {
+ try { return new URL(u, baseUrl || Astro.url).toString() } catch { return u }
+}
+const ogImageAbs = toAbs(ogImage)
+const twitterImage = siteConfig.twitterImage || ogImage
+const twitterImageAbs = toAbs(twitterImage)📝 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.
| const ogImageAbs = ogImage.startsWith("http") ? ogImage : `${baseUrl}${ogImage}` | |
| const twitterImage = siteConfig.twitterImage || ogImage | |
| const twitterImageAbs = twitterImage.startsWith("http") | |
| ? twitterImage | |
| : `${baseUrl}${twitterImage}` | |
| // Replace the following lines in apps/www/src/layouts/root-layout.astro: | |
| // ↓ remove these: | |
| - const ogImageAbs = ogImage.startsWith("http") ? ogImage : `${baseUrl}${ogImage}` | |
| - const twitterImage = siteConfig.twitterImage || ogImage | |
| - const twitterImageAbs = twitterImage.startsWith("http") | |
| - ? twitterImage | |
| - : `${baseUrl}${twitterImage}` | |
| // ↑ with these: | |
| const toAbs = (u: string) => { | |
| try { | |
| return new URL(u, baseUrl || Astro.url).toString() | |
| } catch { | |
| return u | |
| } | |
| } | |
| const ogImageAbs = toAbs(ogImage) | |
| const twitterImage = siteConfig.twitterImage || ogImage | |
| const twitterImageAbs = toAbs(twitterImage) |
🤖 Prompt for AI Agents
In apps/www/src/layouts/root-layout.astro around lines 31 to 35, the current
string concatenation for absolute image URLs can produce malformed URLs when the
path lacks a leading slash; replace the manual concatenation with the URL
constructor to resolve paths against the site base (e.g., use new URL(ogImage,
baseUrl).toString() and new URL(twitterImage, baseUrl).toString() for
resolution), preserve the existing checks for already-absolute URLs, and wrap
URL construction in a safe fallback (try/catch or conditional) to fall back to
the original value if URL() throws.
| <meta name="creator" content={siteName} /> | ||
| <meta name="theme-color" content="#ffffff" /> | ||
|
|
||
| <link rel="canonical" href={canonical} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify site is configured; canonicals must be absolute.
Make sure apps/www/astro.config.ts sets a site URL, and that the OG API route actually lives under /api/og.
🏁 Script executed:
#!/bin/bash
# Verify Astro site config and OG route
echo "Astro config site field:"
fd -a "astro.config.*" apps/www | xargs -I{} rg -n "^\s*site\s*:" {}
echo -e "\nsiteConfig.url presence:"
fd -a site.ts apps/www/src/config | xargs -I{} rg -n "url\s*:" {}
echo -e "\nOG route candidates:"
fd -a --glob "apps/www/src/pages/**/og*.*"
fd -a --glob "apps/www/src/pages/api/og*.*"
echo -e "\nReferences to /api/og vs /og in repo:"
rg -n "/api/og|\\b/og\\b" apps/wwwLength of output: 805
Add missing /api/og API route
– I see ogImage and twitterImage set to /api/og in src/config/site.ts and the layout checks for /api/og, but there’s no file under apps/www/src/pages/api/og.*. Create or correct that route so OG image generation works.
apps/www/src/pages/robots.txt.ts
Outdated
| export const GET: APIRoute = async ({ site }) => { | ||
| const base = site?.toString().replace(/\/$/, "") ?? "" | ||
| const body = `User-agent: * | ||
| Allow: /f | ||
| Sitemap: ${base}/sitemap.xml\n` | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Ensure absolute Sitemap URL; fallback when site is undefined.
In dev/preview, site may be undefined, yielding a relative Sitemap. Compute origin from request as a fallback.
Apply:
-export const GET: APIRoute = async ({ site }) => {
- const base = site?.toString().replace(/\/$/, "") ?? ""
+export const GET: APIRoute = async ({ site, request }) => {
+ const origin =
+ site?.toString().replace(/\/$/, "") ||
+ (request?.url ? new URL(request.url).origin : "")
+ const sitemapUrl = origin ? `${origin}/sitemap.xml` : "/sitemap.xml"
const body = `User-agent: *
-Allow: /f
-Sitemap: ${base}/sitemap.xml\n`
+Allow: /
+Sitemap: ${sitemapUrl}\n`📝 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.
| export const GET: APIRoute = async ({ site }) => { | |
| const base = site?.toString().replace(/\/$/, "") ?? "" | |
| const body = `User-agent: * | |
| Allow: /f | |
| Sitemap: ${base}/sitemap.xml\n` | |
| export const GET: APIRoute = async ({ site, request }) => { | |
| const origin = | |
| site?.toString().replace(/\/$/, "") || | |
| (request?.url ? new URL(request.url).origin : "") | |
| const sitemapUrl = origin ? `${origin}/sitemap.xml` : "/sitemap.xml" | |
| const body = `User-agent: * | |
| Allow: / | |
| Sitemap: ${sitemapUrl}\n` |
🤖 Prompt for AI Agents
In apps/www/src/pages/robots.txt.ts around lines 3 to 8, the Sitemap URL can be
relative when site is undefined; update GET to derive an absolute origin from
the incoming request as a fallback (e.g., use request.headers.get('host') and
request.protocol or construct from request url), trim any trailing slash, and
build Sitemap as `${origin}/sitemap.xml`; ensure you still prefer site when
present and produce a fully qualified HTTPS/HTTP origin so the robots.txt always
contains an absolute Sitemap URL.
apps/www/src/pages/robots.txt.ts
Outdated
| const body = `User-agent: * | ||
| Allow: /f | ||
| Sitemap: ${base}/sitemap.xml\n` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likely typo: “Allow: /f”.
Robots defaults to allow-all; if you intend to allow the entire site explicitly, use “/”, not “/f”.
Apply:
-Allow: /f
+Allow: /📝 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.
| const body = `User-agent: * | |
| Allow: /f | |
| Sitemap: ${base}/sitemap.xml\n` | |
| const body = `User-agent: * | |
| Allow: / | |
| Sitemap: ${base}/sitemap.xml\n` |
🤖 Prompt for AI Agents
In apps/www/src/pages/robots.txt.ts around lines 5 to 7, the robots body
contains a likely typo "Allow: /f" which should be "Allow: /"; update the
template literal to use "Allow: /" (and keep the existing Sitemap interpolation
and trailing newline intact) so the robots file explicitly allows the whole site
instead of the invalid "/f" path.
apps/www/src/pages/sitemap.xml.ts
Outdated
| export const GET: APIRoute = async ({ site }) => { | ||
| const base = site?.toString().replace(/\/$/, "") ?? "" | ||
| const now = new Date().toISOString() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Always produce absolute URLs (spec requires absolute).
Fallback to the request origin when site is undefined.
Apply:
-export const GET: APIRoute = async ({ site }) => {
- const base = site?.toString().replace(/\/$/, "") ?? ""
+export const GET: APIRoute = async ({ site, request }) => {
+ const base =
+ site?.toString().replace(/\/$/, "") || new URL(request.url).origin📝 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.
| export const GET: APIRoute = async ({ site }) => { | |
| const base = site?.toString().replace(/\/$/, "") ?? "" | |
| const now = new Date().toISOString() | |
| export const GET: APIRoute = async ({ site, request }) => { | |
| const base = | |
| site?.toString().replace(/\/$/, "") || new URL(request.url).origin | |
| const now = new Date().toISOString() |
🤖 Prompt for AI Agents
In apps/www/src/pages/sitemap.xml.ts around lines 25 to 27, the sitemap <loc>
values must be absolute but the current code may produce relative URLs when site
is undefined; change the base computation to fall back to the request origin
(e.g. use new URL(request.url).origin) when site is not provided, and still
strip any trailing slash so base = site?.toString().replace(/\/$/, "") ?? new
URL(request.url).origin.replace(/\/$/, "").
| scule: ^1.3.0 | ||
| shadcn: ^2.10.0 | ||
| sharp: ^0.34.3 | ||
| sharp: 0.34.3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pinning sharp is fine, but it will fail on Cloudflare Workers runtime.
Given apps/www uses the Cloudflare adapter (SSR), native sharp cannot execute there. Ensure OG image generation does not depend on sharp at runtime; use @resvg/resvg-wasm instead and gate any Node-only paths for local dev builds.
For the OG route, swap sharp->Resvg:
- const png = sharp(Buffer.from(svg)).png()
- const response = await png.toBuffer()
- const body = new Uint8Array(response)
+ const { Resvg } = await import("@resvg/resvg-wasm")
+ const resvg = new Resvg(svg, { fitTo: { mode: "width", value: 1200 } })
+ const body = resvg.render().asPng()Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In pnpm-workspace.yaml around line 70, the repo pins sharp which cannot run on
Cloudflare Workers; replace usage with @resvg/resvg-wasm for OG image generation
and remove/avoid bundling sharp into the Cloudflare SSR build. Update
dependencies to add @resvg/resvg-wasm (and remove or mark sharp as dev-only),
change the OG route to use resvg-wasm APIs to render SVG->PNG, and guard any
sharp imports behind a Node-only check (e.g., process.env.NODE_ENV or typeof
window === 'undefined' + platform detection) so sharp is only used for local
dev/build scripts; ensure the Worker bundle always resolves the resvg-wasm path
and that build-time tooling excludes sharp from the Cloudflare-targeted outputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/www/src/layouts/root-layout.astro (1)
1-1: Install dependencies & rerun typecheck; add missing/ogroute
- apps/www: install deps (
pnpm install) then runpnpm run typecheckto surface any errors.apps/www/src/config/site.tsreferences/ogbut noapps/www/src/pages/og*exists; create the OG page (e.g.src/pages/og.tsxor.astro) to match.
♻️ Duplicate comments (1)
apps/www/src/config/site.ts (1)
10-11: Fix grammar in user-facing description.See earlier comment; still applicable.
Apply:
- description: - "A set of beautiful designed components you can customize, extend, and make your own. Multi Framework Support, Open Source.", + description: + "Beautifully designed components you can customize, extend, and make your own. Multi‑framework support. Open source.",
🧹 Nitpick comments (13)
packages/react/package.json (1)
1-1: Add a license field to satisfy the pipeline warning.The package is private, but GitHub Actions warns about a missing license. Add
"license": "UNLICENSED"(or the repo’s actual license).Apply near the top-level fields:
{ "name": "@ui/react", "type": "module", "private": true, + "license": "UNLICENSED", "exports": {packages/lib/utils/url.ts (1)
6-10: Guard scheme-only strings and collapse multiple trailing slashes.Edge cases like
"https://"would become"https:/". Also consider//orexample.com//. Suggest guarding scheme-only inputs and trimming one-or-more trailing slashes.Apply:
export function removeTrailingSlash(input: string | URL): string { - const value = typeof input === "string" ? input : input.toString() - if (value === "/") return "/" - return value.replace(/\/$/, "") + const value = typeof input === "string" ? input : input.toString() + // Keep root and scheme-only (e.g., "https://") intact + if (value === "/" || /^[a-zA-Z][\w+\-.]*:\/\/$/.test(value)) return value + // Remove one or more trailing slashes + return value.replace(/\/+$/, "") }Optionally add tests for: "/", "https://", "http://", "https://example.com/", "https://example.com//", "/docs/", new URL("https://example.com/"), new URL("https://example.com/foo/").
apps/www/package.json (2)
21-21: Sitemap integration dependency LGTM.Matches astro.config.ts usage; ensures sitemap resolves at build. Also, please add a "license" field to silence the CI warning.
Apply:
{ "name": "www", "version": "0.0.0", "private": true, "type": "module", + "license": "MIT", "scripts": {
47-47: Deduplicate tailwindcss dependency.You have tailwindcss listed in both dependencies and devDependencies. Keep it in devDependencies only to avoid resolution ambiguity.
Apply:
"dependencies": { - "tailwindcss": "catalog:", }, "devDependencies": { "tailwindcss": "catalog:", }apps/www/public/site.webmanifest (1)
1-19: Solid branding alignment; add PWA fields for better install quality.Add id, start_url, scope, and a maskable icon purpose to improve install behavior.
Apply:
{ "name": "shipbase", "short_name": "shipbase", + "id": "/", + "start_url": "/", + "scope": "/", "icons": [ { "src": "/android-chrome-192x192.png", "sizes": "192x192", - "type": "image/png" + "type": "image/png", + "purpose": "any maskable" }, { "src": "/android-chrome-512x512.png", "sizes": "512x512", - "type": "image/png" + "type": "image/png", + "purpose": "any maskable" } ], "theme_color": "#09090b", "background_color": "#09090b", "display": "standalone" }apps/www/src/pages/examples/index.astro (1)
6-26: Metadata wiring looks good; minor duplication nit."Examples" is repeated in title/alt; optional: hoist to a const to keep in sync.
--- import { siteConfig } from "@/config/site" import RootLayout from "@/layouts/root-layout.astro" --- +--- +const PAGE = "Examples" +--- <RootLayout metadata={{ - title: "Examples", + title: PAGE, description: siteConfig.description, openGraph: { type: "website", - title: "Examples", + title: PAGE, description: siteConfig.description, url: String(Astro.url), siteName: siteConfig.name, - image: { url: siteConfig.ogImage, alt: "Examples" }, + image: { url: siteConfig.ogImage, alt: PAGE }, }, twitter: { card: "summary_large_image", - title: "Examples", + title: PAGE, description: siteConfig.description, creator: siteConfig.creator, - image: { url: siteConfig.twitterImage, alt: "Examples" }, + image: { url: siteConfig.twitterImage, alt: PAGE }, }, }} >Also, PR description mentions /api/og, but siteConfig points to /og. Align docs/description to avoid confusion.
apps/www/astro.config.ts (1)
17-21: Sitemap integration: consider minimal config.If you have draft/hidden routes, add a filter; add i18n when applicable.
integrations: [ mdx({ remarkPlugins: [remarkPluginCodeWrapper] }), react(), - sitemap(), + sitemap({ + filter: (page) => !page.includes("/draft"), + }), ],apps/www/src/pages/examples/[...slug].astro (1)
8-14: Unnecessary Array.prototype.flat(1).getCollection returns a flat array; drop flat(1) to simplify.
- const collections = await getCollection("components") - return collections.flat(1).map((entry) => ({ + const collections = await getCollection("components") + return collections.map((entry) => ({ params: { slug: entry.id }, props: { entry }, }))apps/www/src/pages/docs/components/[component].astro (1)
32-52: Reduce metadata duplication with a helper.Three docs pages construct identical metadata. Consider a small util (e.g., buildPageMetadata(entry, Astro.url)) to centralize defaults and avoid drift.
apps/www/src/pages/index.astro (1)
18-38: Polish title phrasing.Use “Multi‑framework” (hyphen) for consistency with the site’s copy.
Apply:
- title: "Shadcn UI Components for Multi Framework", + title: "Shadcn UI Components for Multi‑framework",apps/www/src/pages/docs/[...slug].astro (1)
24-44: Same metadata duplication as other pages.Extract a shared builder to prevent divergence across docs/index/components pages.
apps/www/src/layouts/root-layout.astro (2)
13-41: Prefer a specific name for the props interface.Use RootLayoutProps to avoid collisions and improve discoverability.
Apply:
-export interface Props { +export interface RootLayoutProps { @@ -const { metadata } = Astro.props as Props +const { metadata } = Astro.props as RootLayoutProps
47-59: Theme-color meta toggling is good; also set media-aware theme-colors.Optional: Add two theme-color tags with media queries to avoid FOUC on first paint.
Example:
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" /> <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#09090b" />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (3)
apps/www/public/og-image.pngis excluded by!**/*.pngapps/www/public/og-image.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
apps/www/astro.config.ts(1 hunks)apps/www/package.json(2 hunks)apps/www/public/site.webmanifest(1 hunks)apps/www/src/config/site.ts(1 hunks)apps/www/src/layouts/root-layout.astro(1 hunks)apps/www/src/pages/404.astro(1 hunks)apps/www/src/pages/docs/[...slug].astro(2 hunks)apps/www/src/pages/docs/components/[component].astro(2 hunks)apps/www/src/pages/examples/[...slug].astro(2 hunks)apps/www/src/pages/examples/index.astro(1 hunks)apps/www/src/pages/index.astro(2 hunks)apps/www/src/pages/robots.txt.ts(1 hunks)packages/lib/utils/url.ts(1 hunks)packages/react/package.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/www/src/pages/404.astro
- apps/www/src/pages/robots.txt.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
packages/lib/utils/url.tsapps/www/astro.config.tsapps/www/src/config/site.ts
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
packages/lib/utils/url.tspackages/react/package.jsonapps/www/package.jsonapps/www/astro.config.tsapps/www/src/config/site.ts
packages/lib/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Shared libraries and utilities must be located under packages/lib/
Keep shared utilities in packages/lib
Files:
packages/lib/utils/url.ts
packages/react/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
React component code must be located under packages/react/
Files:
packages/react/package.json
packages/{react,vue}/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
Place framework-specific component implementations under packages/react and packages/vue
Files:
packages/react/package.json
packages/{react,vue}/**
📄 CodeRabbit inference engine (AGENTS.md)
Place framework-specific component implementations under packages/react and packages/vue
Files:
packages/react/package.json
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/public/site.webmanifestapps/www/src/pages/examples/index.astroapps/www/src/pages/examples/[...slug].astroapps/www/package.jsonapps/www/src/pages/docs/[...slug].astroapps/www/src/layouts/root-layout.astroapps/www/astro.config.tsapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/index.astro
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/public/site.webmanifestapps/www/src/pages/examples/index.astroapps/www/src/pages/examples/[...slug].astroapps/www/package.jsonapps/www/src/pages/docs/[...slug].astroapps/www/src/layouts/root-layout.astroapps/www/astro.config.tsapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/index.astro
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/pages/examples/index.astroapps/www/src/pages/examples/[...slug].astroapps/www/src/pages/docs/[...slug].astroapps/www/src/layouts/root-layout.astroapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/index.astro
apps/www/astro.config.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Files:
apps/www/astro.config.ts
🧠 Learnings (4)
📚 Learning: 2025-08-03T17:49:50.919Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/overview.mdc:0-0
Timestamp: 2025-08-03T17:49:50.919Z
Learning: Applies to packages/{react,vue}/src/components/ui/**/*.{ts,tsx,vue} : Use TypeScript for complete type safety across the codebase
Applied to files:
packages/react/package.json
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: Applies to apps/www/astro.config.ts : Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Applied to files:
apps/www/src/pages/examples/index.astroapps/www/src/pages/examples/[...slug].astroapps/www/package.jsonapps/www/src/pages/docs/[...slug].astroapps/www/src/layouts/root-layout.astroapps/www/astro.config.tsapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/index.astro
📚 Learning: 2025-08-30T18:58:30.749Z
Learnt from: CR
PR: shipbase/ui#0
File: AGENTS.md:0-0
Timestamp: 2025-08-30T18:58:30.749Z
Learning: Applies to apps/www/** : Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Applied to files:
apps/www/src/pages/docs/[...slug].astroapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/index.astro
📚 Learning: 2025-08-03T17:49:32.389Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/component-development.mdc:0-0
Timestamp: 2025-08-03T17:49:32.389Z
Learning: Applies to {packages/react/vite.config.ts,packages/vue/vite.config.ts} : Uses Vite to build component packages with configuration files at packages/react/vite.config.ts and packages/vue/vite.config.ts
Applied to files:
apps/www/astro.config.ts
🧬 Code graph analysis (3)
packages/lib/utils/url.ts (1)
apps/www/src/content.config.ts (2)
entry(8-8)entry(27-28)
apps/www/astro.config.ts (1)
apps/www/src/config/site.ts (1)
siteConfig(1-16)
apps/www/src/pages/docs/components/[component].astro (1)
examples/next/app/layout.tsx (1)
RootLayout(20-34)
🪛 GitHub Actions: Code check
packages/react/package.json
[warning] 1-1: No license field in package.json.
apps/www/public/site.webmanifest
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/src/pages/examples/index.astro
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/src/pages/examples/[...slug].astro
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/package.json
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
[warning] 1-1: No license field in package.json.
apps/www/src/pages/docs/[...slug].astro
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/src/layouts/root-layout.astro
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/astro.config.ts
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/src/config/site.ts
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/src/pages/docs/components/[component].astro
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
apps/www/src/pages/index.astro
[error] 1-1: Typecheck failed. Command '/usr/local/bin/pnpm run typecheck' in '/home/runner/work/ui/ui/apps/www' exited with code 1.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Preview Deployment
🔇 Additional comments (10)
packages/react/package.json (1)
64-64: Confirm single TypeScript version resolution
TypeScript is only declared in the root (catalog:) and in packages/react (catalog:); no other workspace packages list it. Runpnpm why typescript(without-s) to ensure all workspace packages resolve the same TS version and avoid mismatched type checking.packages/lib/utils/url.ts (1)
6-10: LGTM: simple, predictable trailing-slash trim.apps/www/package.json (1)
48-48: tiny-invariant addition is fine.Good for defensive checks in routes/utilities.
apps/www/src/pages/examples/index.astro (1)
2-2: Alias '@/*' already configured in tsconfig.json
apps/www/tsconfig.json defines"paths": { "@/*": ["./src/*"] }, so astro check will resolve@/…imports correctly.apps/www/astro.config.ts (1)
13-13: Correct: sitemap “site” source from config.Ensures absolute URLs in sitemap.
apps/www/src/pages/examples/[...slug].astro (1)
18-37: Metadata block reads well and uses siteConfig consistently.No issues found.
apps/www/src/pages/docs/components/[component].astro (1)
10-10: No action needed: prerender is safe for component docs. Component pages under apps/www/content/components are pure MDX files with no.server.*,.ts, or.tsxcontent and have no runtime/auth dependencies.apps/www/src/pages/index.astro (1)
41-48: LGTM on structural changes.Self-closing separators are fine and keep markup tidy.
apps/www/src/pages/docs/[...slug].astro (1)
10-10: Confirm prerender is appropriate for all overview pages.If any slug embeds client-only logic, prerender may miss it. Spot-check a few entries.
apps/www/src/layouts/root-layout.astro (1)
84-85: Sitemap link relation is non-standard; align with your actual output.Either remove the link, or point to
/sitemap.xmlif that’s what your generator emits (the test plan references/sitemap.xml).Apply:
- <link rel="sitemap" href="/sitemap-index.xml" /> + <!-- Optional: HTML head link not required for sitemaps; robots.txt is authoritative --> + <!-- <link rel="sitemap" href="/sitemap.xml" /> -->
| const { entry } = Astro.props | ||
| --- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type the page props to fix CI “astro check” failure.
Declare Props so Astro infers Astro.props.entry. This is likely the source of the typecheck error.
import RootLayout from "@/layouts/root-layout.astro"
export const getStaticPaths = (async () => {
@@
}) satisfies GetStaticPaths
-const { entry } = Astro.props
+import type { CollectionEntry } from "astro:content"
+export interface Props {
+ entry: CollectionEntry<"components">
+}
+const { entry } = Astro.props📝 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.
| const { entry } = Astro.props | |
| --- | |
| import RootLayout from "@/layouts/root-layout.astro" | |
| import type { CollectionEntry } from "astro:content" | |
| export const getStaticPaths = (async () => { | |
| // …your existing implementation… | |
| }) satisfies GetStaticPaths | |
| export interface Props { | |
| entry: CollectionEntry<"components"> | |
| } | |
| const { entry } = Astro.props |
🤖 Prompt for AI Agents
In apps/www/src/pages/examples/[...slug].astro around lines 15-16, Astro.props
is being used untyped which causes the “astro check” failure; declare a Props
type/interface that matches the shape of entry (or use a minimal type like {
entry: any } if shape unknown) and apply it by casting Astro.props (e.g., const
{ entry } = Astro.props as Props) or by using defineProps with that type so the
compiler can infer Astro.props.entry.
- Switch to static OG image for better performance and reliability - Update text consistency to "Multi-Framework" across site - Add proper error handling to OG image generation - Improve type safety in layout component - Fix canonical URL generation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
apps/www/src/config/site.ts (1)
9-9: Fix grammar in user-facing description.Duplicate of earlier feedback; still reads awkwardly.
Apply:
- "A set of beautiful designed components you can customize, extend, and make your own. Multi-Framework Support, Open Source.", + "Beautifully designed components you can customize, extend, and make your own. Multi‑framework support. Open source.",apps/www/src/layouts/root-layout.astro (3)
108-109: Serve local TS scripts as modules.Add type="module" so browsers load them as ESM.
Apply:
- <script src="@/scripts/framework-sync.ts"></script> - <script src="@/scripts/package-manager-sync.ts"></script> + <script type="module" src="@/scripts/framework-sync.ts"></script> + <script type="module" src="@/scripts/package-manager-sync.ts"></script>
86-86: ClientRouter must be in body, not head.Invalid in ; move into .
Apply:
- <ClientRouter /> @@ <body class="bg-background overscroll-none text-foreground font-sans antialiased" > + <ClientRouter />Also applies to: 111-114
88-107: Inline script cannot import modules; serialize config instead.Remove the import inside is:inline and embed the serialized constant.
Apply:
- <script is:inline> - import { META_THEME_COLORS } from "@/config/site"; + <script is:inline> + const META_THEME_COLORS = /**/ {JSON.stringify(META_THEME_COLORS)} /**/;(Rest of the script stays the same.)
🧹 Nitpick comments (7)
apps/www/src/config/site.ts (1)
3-7: Unify OG endpoint with PR/test plan (/api/og) to avoid drift.The PR description and test plan expect a dynamic OG endpoint at /api/og, but config still points to static assets. Recommend exposing a canonical ogEndpoint and defaulting images to it so consumers stay consistent.
Apply:
export const siteConfig = { name: "shipbase/ui", url: "https://ui.shipbase.xyz", author: "shipbase", creator: "shipbase", - ogImage: "/og-image.png", - twitterImage: "/og-image.png", + // Canonical dynamic OG endpoint used across the app + ogEndpoint: "/api/og", + ogImage: "/api/og", + twitterImage: "/api/og",apps/www/src/pages/og/index.ts (2)
304-314: Tune cache headers and simplify buffer handling.Use a longer CDN s-maxage and stale-while-revalidate; avoid unnecessary Uint8Array copy.
Apply:
- const png = sharp(Buffer.from(svg)).png() - const response = await png.toBuffer() - const body = new Uint8Array(response) - - return new Response(body, { + const body = await sharp(Buffer.from(svg)).png().toBuffer() + return new Response(body, { status: 200, headers: { "Content-Type": "image/png", - "Cache-Control": "s-maxage=1, stale-while-revalidate=59", + "Cache-Control": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400", }, })
315-321: Minor: include colon in error message for readability.Apply:
- return new Response(`Error ${message}`, { + return new Response(`Error: ${message}`, {apps/www/src/layouts/root-layout.astro (2)
13-39: Interface naming: prefer component-scoped name.To match “PascalCase with Props suffix,” consider RootLayoutProps.
Apply:
-export interface Props { +export interface RootLayoutProps { metadata: { // … } } -const { metadata } = Astro.props as Props +const { metadata } = Astro.props as RootLayoutProps
61-69: Normalize to absolute URLs for canonical and images.Some scrapers ignore relative URLs; compute once and reuse.
Apply:
--- -const { metadata } = Astro.props as Props +const { metadata } = Astro.props as Props +const baseUrl = + siteConfig.url?.replace(/\/$/, "") || + Astro.site?.toString().replace(/\/$/, "") || + Astro.url?.origin?.replace(/\/$/, "") || + "" +const toAbs = (u: string) => { + try { return new URL(u, baseUrl || Astro.url).toString() } catch { return u } +} +const canonical = toAbs(String(Astro.url)) +const ogImageAbs = toAbs(metadata.openGraph.image.url) +const twitterImageAbs = toAbs(metadata.twitter.image.url) --- - <meta property="og:url" content={metadata.openGraph.url} /> - <meta property="og:image" content={metadata.openGraph.image.url} /> + <meta property="og:url" content={canonical} /> + <meta property="og:image" content={ogImageAbs} /> @@ - <meta name="twitter:image" content={metadata.twitter.image.url} /> + <meta name="twitter:image" content={twitterImageAbs} /> @@ - <link rel="canonical" href={Astro.url.toString()} /> + <link rel="canonical" href={canonical} />Also applies to: 70-77, 78-78
apps/www/src/pages/docs/[...slug].astro (1)
26-49: Reuse canonical/description vars; prefer Astro.url.hrefReduce duplication and ensure canonical is consistent.
<RootLayout metadata={{ title: entry.data.title, - description: entry.data.description ?? siteConfig.description, + description, openGraph: { type: "article", title: entry.data.title, - description: entry.data.description ?? siteConfig.description, - url: String(Astro.url), + description, + url: canonical, siteName: siteConfig.name, image: { url: ogImage, alt: entry.data.title, }, }, twitter: { card: "summary_large_image", title: entry.data.title, - description: entry.data.description ?? siteConfig.description, + description, creator: siteConfig.creator, image: { url: ogImage, alt: entry.data.title }, }, }} >Note: Ensure siteConfig.creator includes the leading “@” if required by your RootLayout meta generator.
apps/www/src/pages/docs/components/[component].astro (1)
33-53: Deduplicate and use canonical; confirm OG typeUse the computed description/canonical, and double-check if “website” vs “article” is intentional for component docs.
<RootLayout metadata={{ title: entry.data.title, - description: entry.data.description ?? siteConfig.description, + description, openGraph: { - type: "website", + type: "website", title: entry.data.title, - description: entry.data.description ?? siteConfig.description, - url: String(Astro.url), + description, + url: canonical, siteName: siteConfig.name, image: { url: ogImage, alt: entry.data.title }, }, twitter: { card: "summary_large_image", title: entry.data.title, - description: entry.data.description ?? siteConfig.description, + description, creator: siteConfig.creator, image: { url: ogImage, alt: entry.data.title }, }, }} >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
apps/www/public/og-image.pngis excluded by!**/*.png
📒 Files selected for processing (8)
apps/www/src/components/landing/cta.astro(1 hunks)apps/www/src/config/site.ts(1 hunks)apps/www/src/layouts/root-layout.astro(1 hunks)apps/www/src/pages/404.astro(1 hunks)apps/www/src/pages/docs/[...slug].astro(2 hunks)apps/www/src/pages/docs/components/[component].astro(2 hunks)apps/www/src/pages/index.astro(2 hunks)apps/www/src/pages/og/index.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/www/src/pages/404.astro
- apps/www/src/components/landing/cta.astro
- apps/www/src/pages/index.astro
🧰 Additional context used
📓 Path-based instructions (5)
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astroapps/www/src/pages/og/index.ts
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astroapps/www/src/pages/og/index.ts
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astroapps/www/src/pages/og/index.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
apps/www/src/config/site.tsapps/www/src/pages/og/index.ts
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
apps/www/src/config/site.tsapps/www/src/pages/og/index.ts
🧠 Learnings (2)
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: Applies to apps/www/astro.config.ts : Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Applied to files:
apps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/docs/components/[component].astroapps/www/src/layouts/root-layout.astro
📚 Learning: 2025-08-30T18:58:30.749Z
Learnt from: CR
PR: shipbase/ui#0
File: AGENTS.md:0-0
Timestamp: 2025-08-30T18:58:30.749Z
Learning: Applies to apps/www/** : Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Applied to files:
apps/www/src/pages/docs/[...slug].astroapps/www/src/pages/docs/components/[component].astro
🧬 Code graph analysis (2)
apps/www/src/pages/docs/components/[component].astro (1)
examples/next/app/layout.tsx (1)
RootLayout(20-34)
apps/www/src/pages/og/index.ts (1)
apps/www/src/config/site.ts (1)
siteConfig(1-14)
🔇 Additional comments (10)
apps/www/src/config/site.ts (1)
18-21: META_THEME_COLORS export looks good.Matches SCREAMING_SNAKE_CASE guideline and is reused elsewhere.
apps/www/src/pages/og/index.ts (3)
14-49: Nice: fonts are cached in-memory.Eliminates per-request disk I/O.
51-60: FS reads from public/ may break on some adapters.process.cwd()+public works on Node adapters, but can fail on serverless/bundled targets. Consider resolving via URL imports or confirm adapter.
Would you like a patch using new URL(..., import.meta.url) for robust resolution?
1-1: Ignore incorrect route path suggestion: Next.js file-system routing mapspages/og/index.tsto/ogby default (nextjs.org); there is noapps/www/src/pages/api/og/index.tsnor any references to/api/ogin the codebase or tests, so no changes are needed.Likely an incorrect or invalid review comment.
apps/www/src/layouts/root-layout.astro (1)
47-47: Heads-up: keywords tag is largely ignored; fine to keep. Title/author/creator/meta setup looks solid.No action; just noting.
Also applies to: 52-60
apps/www/src/pages/docs/[...slug].astro (3)
6-6: LGTM: Use of siteConfig for metadata fallbacksImport looks right and aligns with centralizing branding/SEO defaults.
10-10: Verify prerender + OG endpoint compatibilityIf the OG route is server-only (e.g., /api/og), confirm apps/www output mode supports it in production. Also reconcile the endpoint path used here (/og vs /api/og) across the app.
21-24: Prevent undefined in OG params and emit absolute image URL (verify endpoint path)
- Fallback description:
const description = entry.data.description ?? siteConfig.description- Build absolute URL:
-const ogImage = `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}` +const ogBase = "/api/og" // adjust to your actual OG endpoint (e.g. "/og" or "/api/og") +const ogPath = `${ogBase}?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(description)}` +const ogImage = new URL(ogPath, Astro.url).toString()- I didn’t find a
/api/ogroute in the codebase—please confirm the correct OG-generation endpoint.apps/www/src/pages/docs/components/[component].astro (2)
6-6: LGTM: siteConfig importConsistent with project-wide SEO defaults.
10-10: Confirm prerender with dynamic OG routeStatic prerender is fine; just ensure your deployed target serves the OG endpoint (likely /api/og) at runtime.
| const { Content, headings } = await render(entry) | ||
| const ogImage = `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}` | ||
| --- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Same OG param/absolute URL fix as other page
Guard undefined description and output an absolute OG URL; align endpoint path.
-const ogImage = `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`
+const canonical = Astro.url.href
+const description = entry.data.description ?? siteConfig.description
+const ogBase = "/api/og"
+const ogPath = `${ogBase}?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(description)}`
+const ogImage = new URL(ogPath, Astro.url).toString()🏁 Script executed:
#!/usr/bin/env bash
set -e
echo "Searching for siteConfig references in [component].astro..."
rg -n "siteConfig" -g 'apps/www/src/pages/docs/components/\[component\].astro'
echo
echo "Searching for siteConfig imports in codebase..."
rg -n "import.*siteConfig" -g 'apps/www/src/**'
echo
echo "Searching for /api/og references..."
rg -n "/api/og" -g 'apps/www/src/**'
echo
echo "Searching for /og? references..."
rg -n "/og\\?" -g 'apps/www/src/**'
echo
echo "Searching for Astro.url usage..."
rg -n "Astro\\.url" -g 'apps/www/src/**'Length of output: 2353
🏁 Script executed:
#!/usr/bin/env bash
set -e
echo "1. Locating OG endpoint implementation file..."
find apps/www/src/pages -type f -path '*/og/index.*'
echo
echo "2. Displaying the first 50 lines of that file to confirm its route and handler signature..."
find apps/www/src/pages -type f -path '*/og/index.*' -exec sed -n '1,50p' {} \;Length of output: 1617
🏁 Script executed:
#!/usr/bin/env bash
set -e
echo "1. Searching for ogImage usage across docs pages..."
rg -nH "ogImage" -C3 -g 'apps/www/src/pages/docs/**'
echo
echo "2. Searching for new URL usage across codebase..."
rg -n "new URL" -g 'apps/www/src/**'
echo
echo "3. Searching for Astro.url usage across docs pages..."
rg -n "Astro.url" -g 'apps/www/src/pages/docs/**'Length of output: 4167
🏁 Script executed:
#!/usr/bin/env bash
set -e
echo "Checking GET handler signature in OG endpoint"
rg -n "export const GET" -g 'apps/www/src/pages/og/index.ts'
echo
echo "Showing first 70 lines of OG endpoint file"
head -n 70 apps/www/src/pages/og/index.tsLength of output: 2248
Use fallback description and build an absolute OG URL with the correct /og endpoint
- Replace
entry.data.descriptionwithentry.data.description ?? siteConfig.description. - Keep
ogBase = "/og"(the API route lives at/og, not/api/og). - Construct the absolute URL via
new URL(ogPath, Astro.url).toString(). - Drop the unused
canonicalvariable.
-const ogImage = `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`
+const description = entry.data.description ?? siteConfig.description
+const ogBase = "/og"
+const ogPath = `${ogBase}?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(description)}`
+const ogImage = new URL(ogPath, Astro.url).toString()📝 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.
| const { Content, headings } = await render(entry) | |
| const ogImage = `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}` | |
| --- | |
| const { Content, headings } = await render(entry) | |
| const description = entry.data.description ?? siteConfig.description | |
| const ogBase = "/og" | |
| const ogPath = `${ogBase}?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(description)}` | |
| const ogImage = new URL(ogPath, Astro.url).toString() | |
| --- |
🤖 Prompt for AI Agents
In apps/www/src/pages/docs/components/[component].astro around lines 28 to 31,
the OG image URL uses entry.data.description directly and builds a relative
path; update it to use a fallback description (entry.data.description ??
siteConfig.description), keep ogBase = "/og", build the OG path and then produce
an absolute URL with new URL(ogPath, Astro.url).toString(), and remove the
now-unused canonical variable; ensure the final ogImage is the absolute URL
constructed this way.
- Replace sharp with @resvg/resvg-wasm for OG image generation - Implement proper WASM initialization with promise caching - Add Cloudflare Workers compatible SVG to PNG conversion - Maintain same image quality and functionality - Fix runtime compatibility issues in edge environments 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
apps/www/package.json (1)
43-46: Switch to satori + resvg-wasm for OG rasterization — good call for Cloudflare Workers.This removes the sharp runtime incompatibility on Workers and aligns with the adapter. LGTM.
🧹 Nitpick comments (3)
apps/www/package.json (1)
47-47: tailwindcss is listed in both dependencies and devDependencies.Keep it only in devDependencies to avoid duplicate installs and version drift.
Apply:
- "tailwindcss": "catalog:",Also applies to: 63-63
apps/www/src/pages/og/index.ts (2)
11-12: Clamp input length to avoid expensive renders and oversized cards.Cap title/description to reasonable lengths.
Apply:
const WIDTH = 1200 const HEIGHT = 630 +const MAX_TITLE_LEN = 140 +const MAX_DESC_LEN = 240 @@ - const title = searchParams.get("title") ?? siteConfig.name - const description = - searchParams.get("description") ?? siteConfig.description + const titleRaw = searchParams.get("title") ?? siteConfig.name + const descriptionRaw = + searchParams.get("description") ?? siteConfig.description + const title = titleRaw.slice(0, MAX_TITLE_LEN) + const description = descriptionRaw.slice(0, MAX_DESC_LEN)Also applies to: 83-86
333-335: Increase CDN cache TTL; current s-maxage=1 will thrash the renderer.Unless you need per-second freshness, bump TTL and allow long stale-while-revalidate.
Apply:
- "Cache-Control": "s-maxage=1, stale-while-revalidate=59", + "Cache-Control": "public, max-age=0, s-maxage=86400, stale-while-revalidate=604800",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (2)
apps/www/package.json(2 hunks)apps/www/src/pages/og/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
apps/www/src/pages/og/index.ts
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
apps/www/src/pages/og/index.tsapps/www/package.json
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/src/pages/og/index.tsapps/www/package.json
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/pages/og/index.ts
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/src/pages/og/index.tsapps/www/package.json
🧠 Learnings (2)
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: Applies to apps/www/astro.config.ts : Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Applied to files:
apps/www/package.json
📚 Learning: 2025-08-30T18:58:30.749Z
Learnt from: CR
PR: shipbase/ui#0
File: AGENTS.md:0-0
Timestamp: 2025-08-30T18:58:30.749Z
Learning: Use pnpm, turborepo, biome, TypeScript, Vitest, Changesets, Ark UI, Tailwind CSS v4, Lucide React, XState, Astro, and MDX as the project tech stack
Applied to files:
apps/www/package.json
🧬 Code graph analysis (1)
apps/www/src/pages/og/index.ts (1)
apps/www/src/config/site.ts (1)
siteConfig(1-14)
🔇 Additional comments (2)
apps/www/package.json (1)
21-21: Sitemap integration and site configuration already present
Theapps/www/astro.config.tsfile imports@astrojs/sitemap, includessitemap()inintegrations, and setssite: siteConfig.url, so no further changes are needed.apps/www/src/pages/og/index.ts (1)
9-9: prerender disabled for a dynamic image route — LGTM.Correct for a server-rendered OG endpoint.
Added resvg.wasm and yoga.wasm files to support server-side image generation for Open Graph images, improving SEO capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Updated the OG image generation code to improve compatibility and functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Fixed WASM imports to use proper URL syntax for bundler compatibility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Updated Astro configuration to properly handle WASM files for OG image generation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/www/src/pages/og/index.ts (2)
4-6: Fix Resvg WASM init for bundlers: import asset URL and init with fetch()Directly importing the .wasm module can fail across adapters/bundlers. Import the asset URL with ?url and pass a Response to initWasm. Also tighten the type of initResvgPromise.
import { Resvg, initWasm } from "@resvg/resvg-wasm" -// @ts-ignore - resvg.wasm is a module -import resvgwasm from "@resvg/resvg-wasm/index_bg.wasm" +// @ts-expect-error Vite resolves this to an asset URL +import resvgWasmUrl from "@resvg/resvg-wasm/index_bg.wasm?url" @@ -let fontsCache: Font[] | null = null -const initResvgPromise: Promise<void> | null = initWasm(resvgwasm) +let fontsCache: Font[] | null = null +const initResvgPromise: Promise<void> = initWasm(fetch(resvgWasmUrl)) const initYogaPromise = initYoga(yogaWasm).then((yoga) => init(yoga))Also applies to: 20-22
1-2: Make asset loading runtime-agnostic (no fs/path) and cache the logoUsing node:fs/path breaks on Workers and can be brittle in monorepos. Fetch assets from public URLs and cache in memory; also cache the logo data URL to avoid per-request I/O/encoding.
-import { readFile } from "node:fs/promises" -import path from "node:path" @@ -let fontsCache: Font[] | null = null +let fontsCache: Font[] | null = null +let logoCacheDark: string | null = null @@ -async function loadFonts() { +async function loadFonts(baseUrl: string) { if (fontsCache) { return fontsCache } - const fontPath = path.join(process.cwd(), "public", "fonts") - const [normal, semibold, mono] = await Promise.all([ - readFile(path.join(fontPath, "Geist-Regular.ttf")), - readFile(path.join(fontPath, "Geist-SemiBold.ttf")), - readFile(path.join(fontPath, "GeistMono-Regular.ttf")), - ]) + const [normal, semibold, mono] = await Promise.all([ + fetchAsset(baseUrl, "/fonts/Geist-Regular.ttf"), + fetchAsset(baseUrl, "/fonts/Geist-SemiBold.ttf"), + fetchAsset(baseUrl, "/fonts/GeistMono-Regular.ttf"), + ]) @@ -async function loadLogo(dark = true) { - const publicPath = path.join(process.cwd(), "public") - // Prefer PNG to avoid external font dependencies inside SVG - const fileName = dark ? "logo-dark.png" : "logo.png" - const filePath = path.join(publicPath, fileName) - const buffer = await readFile(filePath) - const mime = "image/png" - const dataUrl = `data:${mime};base64,${buffer.toString("base64")}` - return dataUrl +async function loadLogo(baseUrl: string, dark = true) { + if (dark && logoCacheDark) return logoCacheDark + const pathname = dark ? "/logo-dark.png" : "/logo.png" + const bytes = await fetchAsset(baseUrl, pathname) + const dataUrl = `data:image/png;base64,${toBase64(bytes)}` + if (dark) logoCacheDark = dataUrl + return dataUrl } @@ - const fonts = await loadFonts() - const logoSrc = await loadLogo(true) + const fonts = await loadFonts(url.href) + const logoSrc = await loadLogo(url.href, true)Add these helpers near the top (after constants):
function toBase64(bytes: Uint8Array): string { let bin = "" const chunk = 0x8000 for (let i = 0; i < bytes.length; i += chunk) { bin += String.fromCharCode(...bytes.subarray(i, i + chunk)) } // btoa is available in runtime; if not, replace with Buffer.from on Node. return btoa(bin) } async function fetchAsset(baseUrl: string, pathname: string): Promise<Uint8Array> { const url = new URL(pathname, baseUrl) const res = await fetch(url) if (!res.ok) throw new Error(`Asset fetch failed: ${pathname} (${res.status})`) return new Uint8Array(await res.arrayBuffer()) }Also applies to: 19-19, 23-33, 58-67, 79-81
🧹 Nitpick comments (2)
apps/www/src/pages/og/index.ts (2)
320-323: Avoid redundant Uint8Array allocationimage.asPng() already returns a Uint8Array; no need to wrap again.
- const pngBuffer = image.asPng() - - return new Response(new Uint8Array(pngBuffer), { + const body = image.asPng() + return new Response(body, {
322-328: Strengthen CDN caching for OG imagesCurrent s-maxage=1 produces near-zero cache hit rate. Bump TTLs; OG images are deterministic for a given query string.
- "Cache-Control": "s-maxage=1, stale-while-revalidate=59", + "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/www/src/pages/og/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
apps/www/src/pages/og/index.ts
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
apps/www/src/pages/og/index.ts
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/src/pages/og/index.ts
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/pages/og/index.ts
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/src/pages/og/index.ts
🧬 Code graph analysis (1)
apps/www/src/pages/og/index.ts (1)
apps/www/src/config/site.ts (1)
siteConfig(1-14)
🔇 Additional comments (1)
apps/www/src/pages/og/index.ts (1)
69-69: Confirm OG endpoint path references
- No occurrences of
/api/ogremain inapps/www; dynamic OG images use the/og?…route and the static fallback is/og-image.pngviasiteConfig.- Update any external docs (README, test plan) still referencing
/api/ogto/ogor/og-image.pngas appropriate.
apps/www/src/pages/og/index.ts
Outdated
| const { searchParams } = new URL(url) | ||
|
|
||
| const title = searchParams.get("title") ?? siteConfig.name | ||
| const description = | ||
| searchParams.get("description") ?? siteConfig.description | ||
|
|
||
| const fonts = await loadFonts() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use URL directly and clamp input to prevent pathological renders
Avoid constructing a new URL from a URL. Also limit title/description length to bound render cost and output size.
- const { searchParams } = new URL(url)
-
- const title = searchParams.get("title") ?? siteConfig.name
- const description =
- searchParams.get("description") ?? siteConfig.description
+ const { searchParams } = url
+ const title = (searchParams.get("title") ?? siteConfig.name).slice(0, 120)
+ const description = (
+ searchParams.get("description") ?? siteConfig.description
+ ).slice(0, 240)📝 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.
| const { searchParams } = new URL(url) | |
| const title = searchParams.get("title") ?? siteConfig.name | |
| const description = | |
| searchParams.get("description") ?? siteConfig.description | |
| const fonts = await loadFonts() | |
| const { searchParams } = url | |
| const title = (searchParams.get("title") ?? siteConfig.name).slice(0, 120) | |
| const description = ( | |
| searchParams.get("description") ?? siteConfig.description | |
| ).slice(0, 240) | |
| const fonts = await loadFonts() |
🤖 Prompt for AI Agents
In apps/www/src/pages/og/index.ts around lines 73-79, stop unconditionally doing
new URL(url); instead, if the incoming url is already a URL instance use it
directly (if it's a string create a new URL), to avoid unnecessary re-parsing.
When reading searchParams for title and description, trim and clamp them to
fixed maxima (e.g. MAX_TITLE = 100 chars, MAX_DESCRIPTION = 200 chars) and fall
back to siteConfig values if empty after trimming; this bounds render cost and
output size. Ensure the clamped values are used downstream and keep loadFonts()
call as-is.
Refactored loadFonts function to use direct imports from @/assets/fonts/ instead of fetching from Google Fonts or using file system operations. This approach is Cloudflare Workers compatible and eliminates network requests for font loading. Key changes: - Direct TTF imports from local assets (Satori compatible format) - Removed async/await and fetch calls - Simplified font loading to use imported font data directly - Maintained font caching for performance 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (4)
apps/www/src/pages/og/index.ts (3)
3-5: Fix WASM init for bundlers/workers: import as URL and fetch before initWasm.Directly importing the .wasm module can break on Cloudflare Workers/Vite. Use the asset URL with ?url and fetch it for initWasm.
Apply:
-import { Resvg, initWasm } from "@resvg/resvg-wasm" -// @ts-ignore - resvg.wasm is a module -import resvgwasm from "@resvg/resvg-wasm/index_bg.wasm" +import { Resvg, initWasm } from "@resvg/resvg-wasm" +// @ts-expect-error Vite will convert this to an asset URL +import resvgWasmUrl from "@resvg/resvg-wasm/index_bg.wasm?url" @@ -const initResvgPromise: Promise<void> | null = initWasm(resvgwasm) +const initResvgPromise = fetch(resvgWasmUrl).then(initWasm)Also applies to: 18-19
79-79: Embed logo as data: URL; Resvg won’t fetch externalresources.
Using a remote URL risks the logo not rendering. Fetch bytes server-side and inline as data URL (cache it).
Apply:
- const logoSrc = new URL("/logo-dark.png", url.origin).toString() + const logoSrc = await getLogoDataUrl(url.origin, true)Add near the top of this file:
let logoCache: string | null = null function bytesToBase64(bytes: Uint8Array): string { let bin = "" const chunk = 0x8000 for (let i = 0; i < bytes.length; i += chunk) { bin += String.fromCharCode(...bytes.subarray(i, i + chunk)) } return btoa(bin) } async function getLogoDataUrl(baseUrl: string, dark = true): Promise<string> { if (logoCache) return logoCache const pathname = dark ? "/logo-dark.png" : "/logo.png" const res = await fetch(new URL(pathname, baseUrl)) if (!res.ok) throw new Error(`Logo fetch failed: ${pathname} (${res.status})`) const bytes = new Uint8Array(await res.arrayBuffer()) logoCache = `data:image/png;base64,${bytesToBase64(bytes)}` return logoCache }
75-78: Clamp and sanitize query inputs to bound render cost and output size.Prevents pathological titles/descriptions from degrading perf.
Apply:
- const title = searchParams.get("title") ?? siteConfig.name - const description = - searchParams.get("description") ?? siteConfig.description + const title = (searchParams.get("title") ?? siteConfig.name).trim().slice(0, 120) + const description = (searchParams.get("description") ?? siteConfig.description) + .trim() + .slice(0, 240)apps/www/src/pages/examples/[...slug].astro (1)
16-16: Type Astro.props to satisfy “astro check” and improve DX.Define Props and cast Astro.props so
entryis typed. This also matches an earlier review note.Apply:
@@ -import type { GetStaticPaths } from "astro" +import type { GetStaticPaths } from "astro" +import type { CollectionEntry } from "astro:content" @@ -const { entry } = Astro.props +interface Props { + entry: CollectionEntry<"components"> +} +const { entry } = Astro.props as PropsOptional but recommended: explicitly type the catch‑all param and split the id.
@@ - return collections.flat(1).map((entry) => ({ - params: { slug: entry.id }, + return collections.map((entry) => ({ + params: { slug: entry.id.split("/") }, props: { entry }, })) -}) satisfies GetStaticPaths +}) satisfies GetStaticPaths<{ slug: string[] }>Run a quick check:
#!/bin/bash rg -nC2 'GetStaticPaths' apps/www/src/pages/examples/\[\.\.\.slug\].astro rg -n 'Astro\.props as Props|CollectionEntry<"components">' apps/www/src/pages/examples/\[\.\.\.slug\].astro
🧹 Nitpick comments (8)
apps/www/astro.config.ts (4)
9-9: Makesiteenv-overridable to avoid wrong canonical/sitemap URLs in previews.Using
siteConfig.urlis fine for prod, but previews (CF Pages preview, Vercel/Netlify, etc.) will emit production URLs in<link rel="canonical">and/sitemap.xml. Consider an env fallback.import { siteConfig } from "./src/config/site" +const SITE_URL = process.env.SITE_URL ?? process.env.ASTRO_SITE ?? siteConfig.url export default defineConfig({ - site: siteConfig.url, + site: SITE_URL,If you want, I can wire this to your deploy env (Cloudflare Pages
CF_PAGES_URL) and gate robots accordingly.Also applies to: 13-13
15-17: ConfirmimageService: "compile"behavior with Cloudflare adapter.Double-check that this option does what you intend (compile-time assets, no runtime Sharp). If the goal is strictly compile-time image generation for
<Image>, you can also set Astro’s image service explicitly to a WASM/compile-time service to avoid accidental Node-native usage.Example alternative in top-level config (outside adapter):
image: { service: { // compile-time service; avoids Node-native sharp on Workers entrypoint: "astro/assets/services/squoosh" } }Please verify OG image generation code is using an edge-safe pipeline (e.g., satori + @resvg/resvg-wasm) and that no
sharpimports remain.
19-23: Tune sitemap to exclude API/404/OG routes.By default, sitemap may pick up unwanted routes. Exclude endpoints like
/api/*,/_routes, and404(and your OG HTML/PNG routes if any), to prevent indexing noise.- sitemap(), + sitemap({ + filter: (page) => + !page.startsWith("/api/") && + page !== "/404" && + !page.startsWith("/og") // adjust if your OG route path differs + }),
27-39: Avoid externalizing Node built-ins on Workers; prefer targetingwebworker.Listing
node:*modules undervite.ssr.externalleaves barenode:imports in the bundle—these don’t exist on Cloudflare Workers and can 500 at runtime if referenced. Prefer Vite SSR targeting “webworker” and ensure dependencies that conditionally import Node paths are bundled or aliased out.vite: { plugins: [inspect(), tailwindcss()], - ssr: { - external: [ - "node:path", - "node:fs", - "node:module", - "node:fs/promises", - "node:v8", - "node:url", - "node:process", - "node:assert", - "node:util", - ], - }, + ssr: { + target: "webworker", + }, },Optional hardening (outside this block), if you have dev-only code paths pulling Node built-ins:
resolve: { alias: { "node:fs": false, "node:path": false, "node:url": false, "node:process": false, "node:assert": false, "node:util": false, "node:module": false, "node:v8": false, }, }Please also confirm no server-only code paths are reachable in the Worker runtime.
apps/www/src/pages/og/index.ts (2)
99-105: Strengthen caching headers for CDN efficiency.One-second s-maxage defeats caching. Use a more practical TTL and SWR.
Apply:
- headers: { - "Content-Type": "image/png", - "Cache-Control": "s-maxage=1, stale-while-revalidate=59", - }, + headers: { + "Content-Type": "image/png", + "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400", + },
106-113: Don’t cache error responses.Prevent CDNs from caching failures.
Apply:
- return new Response(`Error ${message}`, { + return new Response(`Error ${message}`, { status: 500, - headers: { "Content-Type": "text/plain; charset=utf-8" }, + headers: { + "Content-Type": "text/plain; charset=utf-8", + "Cache-Control": "no-store", + }, })apps/www/src/components/open-graph.tsx (2)
24-24: Drop redundant truthiness check.title is typed as string; simplify.
Apply:
- const TITLE_FONT_SIZE = title && title.length > 20 ? 64 : 80 + const TITLE_FONT_SIZE = title.length > 20 ? 64 : 80
165-170: Make alt text contextual.Use the title as alt for better semantics (even though it won’t affect the rendered PNG).
Apply:
- <img + <img src={logoSrc} width={LOGO_SIZE} height={LOGO_SIZE} - alt="shipbase/ui" + alt={title} style={{ display: "block" }} />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
apps/www/astro.config.ts(1 hunks)apps/www/package.json(3 hunks)apps/www/src/components/open-graph.tsx(1 hunks)apps/www/src/pages/examples/[...slug].astro(1 hunks)apps/www/src/pages/og/index.ts(1 hunks)apps/www/wrangler.jsonc(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/www/wrangler.jsonc
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/www/package.json
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{tsx,vue}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{tsx,vue}: Component variable and function names must use PascalCase (e.g. Button, ToggleGroup)
Prefer Tailwind built-in classes for styling
Use semantic design tokens for styling
Support dark theme in component styles
Use Ark UI components as foundation for accessibility
Support keyboard navigation in components
Provide appropriate ARIA attributes in components
Support screen readers in components
Avoid unnecessary re-renders in components
Handle event listeners properly in components
Files:
apps/www/src/components/open-graph.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
apps/www/src/components/open-graph.tsxapps/www/src/pages/og/index.tsapps/www/astro.config.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
Organize complex styles through CVA (Class Variance Authority)
Files:
apps/www/src/components/open-graph.tsx
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
apps/www/src/components/open-graph.tsxapps/www/src/pages/og/index.tsapps/www/astro.config.ts
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/src/components/open-graph.tsxapps/www/src/pages/og/index.tsapps/www/src/pages/examples/[...slug].astroapps/www/astro.config.ts
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/components/open-graph.tsxapps/www/src/pages/og/index.tsapps/www/src/pages/examples/[...slug].astro
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/src/components/open-graph.tsxapps/www/src/pages/og/index.tsapps/www/src/pages/examples/[...slug].astroapps/www/astro.config.ts
apps/www/astro.config.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Files:
apps/www/astro.config.ts
🧠 Learnings (2)
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: Applies to apps/www/astro.config.ts : Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Applied to files:
apps/www/src/pages/examples/[...slug].astroapps/www/astro.config.ts
📚 Learning: 2025-08-03T17:49:32.389Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/component-development.mdc:0-0
Timestamp: 2025-08-03T17:49:32.389Z
Learning: Applies to {packages/react/vite.config.ts,packages/vue/vite.config.ts} : Uses Vite to build component packages with configuration files at packages/react/vite.config.ts and packages/vue/vite.config.ts
Applied to files:
apps/www/astro.config.ts
🧬 Code graph analysis (2)
apps/www/src/pages/og/index.ts (2)
apps/www/src/config/site.ts (1)
siteConfig(1-14)apps/www/src/components/open-graph.tsx (1)
OpenGraph(17-205)
apps/www/astro.config.ts (1)
apps/www/src/config/site.ts (1)
siteConfig(1-14)
🔇 Additional comments (5)
apps/www/astro.config.ts (2)
4-4: Sitemap integration looks correct.Importing and enabling
@astrojs/sitemapis appropriate, especially withsiteset below. No issues here.
42-42: Confirm SSR output is intended for Cloudflare Workers.
output: "server"with the Cloudflare adapter is correct for SSR. Just verify deployment targets (Pages Functions vs. Workers) and that routes like/api/ogaren’t unintentionally pre-rendered.apps/www/src/pages/og/index.ts (1)
65-74: Ensure dynamic OG endpoint is used consistently
siteConfig.ogImage and siteConfig.twitterImage still point to/og-image.png, and most pages’ metadata reference these static values; only the docs routes construct and use the/og?...URL. If you intend site-wide dynamic OG images, update siteConfig or each page’s metadata to point to the/ogendpoint.apps/www/src/pages/examples/[...slug].astro (2)
5-5: LGTM: Centralized SEO defaults via siteConfig.Importing siteConfig keeps metadata consistent across pages.
8-8: LGTM: prerender is appropriate for examples.Static generation here aligns with content collections.
…ding - Upgrade satori to v0.15.2 for better performance - Replace PNG logo with inline SVG for sharper rendering - Switch to Google Fonts API for dynamic font loading - Increase guide thickness for better visibility - Add upload script for Cloudflare Workers deployment - Improve cache headers for better performance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
apps/www/src/pages/og/index.ts (2)
87-90: Clamp and sanitize inputs to bound render cost.Limit title/description length (and trim) to avoid pathological SVG layouts.
- const title = searchParams.get("title") ?? siteConfig.name - const description = - searchParams.get("description") ?? siteConfig.description + const title = (searchParams.get("title") ?? siteConfig.name).trim().slice(0, 120) + const description = (searchParams.get("description") ?? siteConfig.description) + .trim() + .slice(0, 240)
3-11: Make WASM imports bundler/Worker-safe (?url+ fetch) and tighten types.Use asset-URL imports and fetch for Resvg (and optionally Yoga) to avoid adapter/bundler resolution issues on Cloudflare Workers. Also,
initResvgPromiseisn’t nullable.-import { Resvg, initWasm } from "@resvg/resvg-wasm" -// @ts-ignore - resvg.wasm is a module -import resvgwasm from "@resvg/resvg-wasm/index_bg.wasm" +import { Resvg, initWasm } from "@resvg/resvg-wasm" +// @ts-expect-error - Vite resolves to asset URL +import resvgWasmUrl from "@resvg/resvg-wasm/index_bg.wasm?url" @@ -const initResvgPromise: Promise<void> | null = initWasm(resvgwasm) +const initResvgPromise: Promise<void> = fetch(resvgWasmUrl).then((r) => + initWasm(r) +)Optional (same pattern for Yoga if you hit runtime import issues):
-// @ts-ignore -import yogaWasm from "yoga-wasm-web/dist/yoga.wasm" +// @ts-expect-error - Vite resolves to asset URL +import yogaWasmUrl from "yoga-wasm-web/dist/yoga.wasm?url" @@ -const initYogaPromise = initYoga(yogaWasm).then((yoga) => init(yoga)) +const initYogaPromise = fetch(yogaWasmUrl) + .then((r) => r.arrayBuffer()) + .then((buf) => initYoga(buf)) + .then((yoga) => init(yoga))Also applies to: 19-21
🧹 Nitpick comments (4)
apps/www/src/pages/og/index.ts (2)
19-19: Type nit: remove nullable union.
initWasm(...)always returns a Promise; drop| null.-const initResvgPromise: Promise<void> | null = initWasm(resvgwasm) +const initResvgPromise: Promise<void> = /* see WASM import fix */ null as anyReplace with the concrete init per the WASM import refactor above.
116-124: Minor: fix error message prefix and content-type casing.Small polish for diagnostics consistency.
- return new Response(`Error ${message}`, { + return new Response(`Error: ${message}`, { status: 500, - headers: { "Content-Type": "text/plain; charset=utf-8" }, + headers: { "content-type": "text/plain; charset=utf-8" }, })apps/www/package.json (2)
10-11: Reconsider repurposing “preview” to run Wrangler.“preview” typically serves the production build (astro preview). If tooling/CI expects that, this rename may surprise. Consider adding a dedicated script (e.g., cf:dev) and keep preview as-is.
- "preview": "wrangler dev", - "upload": "wrangler versions upload", + "preview": "astro preview", + "cf:dev": "wrangler dev", + "upload": "wrangler versions upload",
1-76: Fix pipeline warning: add a license field.Monorepo apps can still declare a license to satisfy checks. Use “UNLICENSED” if appropriate.
{ "name": "www", "version": "0.0.0", "private": true, + "license": "MIT", "type": "module",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
apps/www/package.json(3 hunks)apps/www/src/components/open-graph.tsx(1 hunks)apps/www/src/pages/og/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
apps/www/src/pages/og/index.tsapps/www/src/components/open-graph.tsx
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
apps/www/src/pages/og/index.tsapps/www/src/components/open-graph.tsxapps/www/package.json
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/src/pages/og/index.tsapps/www/src/components/open-graph.tsxapps/www/package.json
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/pages/og/index.tsapps/www/src/components/open-graph.tsx
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/src/pages/og/index.tsapps/www/src/components/open-graph.tsxapps/www/package.json
**/*.{tsx,vue}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{tsx,vue}: Component variable and function names must use PascalCase (e.g. Button, ToggleGroup)
Prefer Tailwind built-in classes for styling
Use semantic design tokens for styling
Support dark theme in component styles
Use Ark UI components as foundation for accessibility
Support keyboard navigation in components
Provide appropriate ARIA attributes in components
Support screen readers in components
Avoid unnecessary re-renders in components
Handle event listeners properly in components
Files:
apps/www/src/components/open-graph.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
Organize complex styles through CVA (Class Variance Authority)
Files:
apps/www/src/components/open-graph.tsx
🧠 Learnings (2)
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: The project must use Turbo, Vite, and Astro as build tools
Applied to files:
apps/www/package.json
📚 Learning: 2025-08-30T18:58:30.749Z
Learnt from: CR
PR: shipbase/ui#0
File: AGENTS.md:0-0
Timestamp: 2025-08-30T18:58:30.749Z
Learning: Use pnpm, turborepo, biome, TypeScript, Vitest, Changesets, Ark UI, Tailwind CSS v4, Lucide React, XState, Astro, and MDX as the project tech stack
Applied to files:
apps/www/package.json
🧬 Code graph analysis (1)
apps/www/src/pages/og/index.ts (2)
apps/www/src/config/site.ts (1)
siteConfig(1-14)apps/www/src/components/open-graph.tsx (1)
OpenGraph(16-197)
🪛 GitHub Actions: Code check
apps/www/package.json
[warning] 1-1: No license field in package.json.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Preview Deployment
🔇 Additional comments (6)
apps/www/src/pages/og/index.ts (1)
80-115: Overall route structure looks solid.Good use of try/catch, WASM init gating, and long-lived caching headers for query-keyed images.
apps/www/package.json (4)
22-22: Sitemap dependency addition LGTM.Aligns with the Astro config’s sitemap integration.
27-27: Resvg WASM dependency LGTM.Matches the server OG route; removes need for sharp on Workers.
45-45: Pinned satori version LGTM.Stable for WASM flow; keep in sync with yoga-wasm-web.
49-51: Support deps LGTM.tiny-invariant, tw-animate-css, and yoga-wasm-web align with usage.
apps/www/src/components/open-graph.tsx (1)
32-196: Component structure and styling are clean.Good separation of layout constants and predictable sizing for Satori rendering.
…ding Updated multiple pages and components to support enhanced Open Graph image generation with better SEO metadata and visual consistency across the documentation site. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Convert stroke-width and stroke-linecap to strokeWidth and strokeLinecap in the SVG logo to fix JSX compilation issues. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (8)
apps/www/src/pages/examples/[...slug].astro (1)
15-15: Type Astro.props to satisfy “astro check”Declare a Props interface so the compiler can infer Astro.props.entry.
-import type { GetStaticPaths } from "astro" +import type { GetStaticPaths } from "astro" +import type { CollectionEntry } from "astro:content" @@ -const { entry } = Astro.props +export interface ExamplesPageProps { + entry: CollectionEntry<"components"> +} +const { entry } = Astro.props as ExamplesPagePropsapps/www/src/pages/docs/components/[component].astro (1)
29-33: Guard description and build OG URL against a stable originSame fix applied elsewhere; also ensures absolute URL.
+import { siteConfig } from "@/config/site" @@ -const url = Astro.url -const imageURL = new URL( - `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`, - url.origin -).toString() +const url = Astro.url +const origin = Astro.site ?? url.origin +const desc = entry.data.description ?? siteConfig.description +const imageURL = new URL( + `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(desc)}`, + origin +).toString()apps/www/src/config/site.ts (2)
9-10: Fix grammar and punctuation in user-facing description.Use concise, correct phrasing.
- description: - "A set of beautiful designed components you can customize, extend, and make your own. Multi-Framework Support, Open Source.", + description: + "Beautifully designed components you can customize, extend, and make your own. Multi‑framework support. Open source.",
6-7: Standardize OG endpoint to /api/og and expose a single canonical property.Aligns with PR objective and prevents broken previews.
- ogImage: "/og-image.png", - twitterImage: "/og-image.png", + // Dynamic OG images via Satori endpoint + ogEndpoint: "/api/og", + ogImage: "/api/og", + twitterImage: "/api/og",Follow-up: update consumers to prefer
siteConfig.ogEndpointwhen composing dynamic URLs.apps/www/src/layouts/root-layout.astro (4)
57-65: Emit absolute URLs for canonical and OG/Twitter images.Add in frontmatter:
const baseUrl = siteConfig.url?.replace(/\/$/, "") || Astro.site?.toString().replace(/\/$/, "") || Astro.url?.origin?.replace(/\/$/, "") || "" const toAbs = (u: string) => { try { return new URL(u, baseUrl || Astro.url).toString() } catch { return u } } const canonical = toAbs(url.toString()) const ogImageAbs = toAbs(metadata.openGraph.image.url) const twitterImageAbs = toAbs(metadata.twitter.image.url)Then update tags:
- <meta property="og:url" content={url.toString()} /> + <meta property="og:url" content={canonical} /> @@ - <meta property="og:image" content={metadata.openGraph.image.url} /> + <meta property="og:image" content={ogImageAbs} /> @@ - <meta name="twitter:image" content={metadata.twitter.image.url} /> + <meta name="twitter:image" content={twitterImageAbs} /> @@ - <link rel="canonical" href={Astro.url.toString()} /> + <link rel="canonical" href={canonical} />Also applies to: 66-73, 74-74
104-105: Serve local TS scripts as ES modules.- <script src="@/scripts/framework-sync.ts"></script> - <script src="@/scripts/package-manager-sync.ts"></script> + <script type="module" src="@/scripts/framework-sync.ts"></script> + <script type="module" src="@/scripts/package-manager-sync.ts"></script>
82-82: Move out of .Custom elements aren’t valid in head; render in body.
- <ClientRouter /> @@ <body class="bg-background overscroll-none text-foreground font-sans antialiased" > + <ClientRouter />Also applies to: 107-110
84-86: Inline scripts cannot import modules; serialize the constant instead.- <script is:inline> - import { META_THEME_COLORS } from "@/config/site"; + <script is:inline> + const META_THEME_COLORS = {JSON.stringify(META_THEME_COLORS)};
🧹 Nitpick comments (12)
apps/www/src/components/mobile-nav/sheet.tsx (1)
17-19: Prefer Tailwind token and align with overlay z-index scaleUse the built-in z-50 (or the library’s CSS var) instead of an arbitrary value. This keeps consistency with other overlays and avoids specificity issues.
- <SheetContent className="z-[50] pr-0" side="left"> + <SheetContent className="z-50 pr-0" side="left">If @ui/react/sheet supports a CSS var z-(--z-index), prefer setting the variable on this instance rather than hardcoding the class.
apps/www/src/pages/index.astro (1)
11-19: Resolve OG image against a stable origin (Astro.site when available)Use Astro.site when defined (build-time canonical), falling back to request origin. This avoids mismatched origins in prerendered deployments.
-const url = Astro.url +const url = Astro.url +const origin = Astro.site ?? url.origin @@ -const imageUrl = new URL(siteConfig.ogImage, url.origin).toString() +const imageUrl = new URL(siteConfig.ogImage, origin).toString()apps/www/src/pages/examples/[...slug].astro (2)
17-21: Guard undefined description and use a stable base originFallback to siteConfig.description and prefer Astro.site when present. Also keep the OG route consistent.
+import { siteConfig } from "@/config/site" @@ -const url = Astro.url -const imageURL = new URL( - `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`, - url.origin -).toString() +const url = Astro.url +const origin = Astro.site ?? url.origin +const desc = entry.data.description ?? siteConfig.description +const imageURL = new URL( + `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(desc)}`, + origin +).toString()
24-40: Add alt text to images in metadataIncluding alt improves accessibility and richer previews.
openGraph: { type: "website", title: `${entry.data.title} Examples`, description: entry.data.description, - image: { url: imageURL }, + image: { url: imageURL, alt: `${entry.data.title} – Examples` }, }, twitter: { card: "summary_large_image", title: `${entry.data.title} Examples`, description: entry.data.description, - image: { url: imageURL }, + image: { url: imageURL, alt: `${entry.data.title} – Examples` }, },apps/www/src/pages/docs/[...slug].astro (2)
22-26: Make OG URL absolute using a stable origin and guard descriptionMirror the pattern used elsewhere for reliability.
-const url = Astro.url -const imageURL = new URL( - `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`, - url.origin -).toString() +import { siteConfig } from "@/config/site" +const url = Astro.url +const origin = Astro.site ?? url.origin +const desc = entry.data.description ?? siteConfig.description +const imageURL = new URL( + `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(desc)}`, + origin +).toString()
33-44: Add alt text to OG/Twitter imagesSmall accessibility improvement.
openGraph: { type: "article", title: entry.data.title, description: entry.data.description, - image: { url: imageURL }, + image: { url: imageURL, alt: `${entry.data.title}` }, }, @@ - image: { url: imageURL }, + image: { url: imageURL, alt: `${entry.data.title}` },apps/www/src/pages/docs/components/[component].astro (2)
41-45: Consider OpenGraph type "article" for component docsComponent documentation pages typically map better to “article” than “website”.
- type: "website", + type: "article",
46-51: Add alt text to OG/Twitter imagesImprove accessibility and preview quality.
- image: { url: imageURL }, + image: { url: imageURL, alt: `${entry.data.title}` }, @@ - image: { url: imageURL }, + image: { url: imageURL, alt: `${entry.data.title}` },apps/www/src/config/site.ts (2)
5-5: Separate creator name from Twitter handle.
twitter:creatorexpects a handle (e.g., @user); keepcreatoras human-readable.creator: "shipbase", + twitterHandle: "@shipba_se", @@ links: { - twitter: "https://x.com/shipba_se", + twitter: "https://x.com/shipba_se", },Then in layout, use
siteConfig.twitterHandlefortwitter:creator/twitter:site.Also applies to: 12-12
19-22: Freeze theme colors to prevent accidental mutation and keep literal types.-export const META_THEME_COLORS = { +export const META_THEME_COLORS = { light: "#ffffff", dark: "#09090b", -} +} as constapps/www/src/layouts/root-layout.astro (2)
13-34: Rename interface to be descriptive and extend for robots.-export interface Props { +export interface RootLayoutProps { metadata: { title: string description: string openGraph: { type: "website" | "article" | "book" | "profile" | (string & {}) title: string description: string image: { url: string } } twitter: { card: "summary" | "summary_large_image" | "app" | "player" title: string description: string image: { url: string } } + // Optional per-page robots (e.g., "noindex,follow" for 404) + robots?: string } }Also update usage:
-const { metadata } = Astro.props as Props +const { metadata } = Astro.props as RootLayoutPropsAnd compute once in frontmatter:
const robots = metadata.robots ?? "index,follow"
72-72: Use a proper Twitter handle for creator.Pull from config to avoid plain name here.
- <meta name="twitter:creator" content={siteConfig.creator} /> + <meta name="twitter:creator" content={siteConfig.twitterHandle ?? "@shipba_se"} />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
apps/www/src/components/mobile-nav/sheet.tsx(1 hunks)apps/www/src/config/site.ts(1 hunks)apps/www/src/layouts/root-layout.astro(1 hunks)apps/www/src/pages/404.astro(1 hunks)apps/www/src/pages/docs/[...slug].astro(2 hunks)apps/www/src/pages/docs/components/[component].astro(2 hunks)apps/www/src/pages/examples/[...slug].astro(1 hunks)apps/www/src/pages/examples/index.astro(1 hunks)apps/www/src/pages/index.astro(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/www/src/pages/examples/index.astro
- apps/www/src/pages/404.astro
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{tsx,vue}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{tsx,vue}: Component variable and function names must use PascalCase (e.g. Button, ToggleGroup)
Prefer Tailwind built-in classes for styling
Use semantic design tokens for styling
Support dark theme in component styles
Use Ark UI components as foundation for accessibility
Support keyboard navigation in components
Provide appropriate ARIA attributes in components
Support screen readers in components
Avoid unnecessary re-renders in components
Handle event listeners properly in components
Files:
apps/www/src/components/mobile-nav/sheet.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
**/*.{ts,tsx}: Function names must use camelCase (e.g. buttonVariants, cn)
Constant names must use SCREAMING_SNAKE_CASE (e.g. DEFAULT_VARIANT)
Type interfaces must use PascalCase with Props suffix (e.g. ButtonProps)
Files:
apps/www/src/components/mobile-nav/sheet.tsxapps/www/src/config/site.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/code-standards.mdc)
Organize complex styles through CVA (Class Variance Authority)
Files:
apps/www/src/components/mobile-nav/sheet.tsx
**/*.{js,jsx,ts,tsx,json,jsonc}
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Maintain code quality and style as configured in biome.jsonc
Files:
apps/www/src/components/mobile-nav/sheet.tsxapps/www/src/config/site.ts
apps/www/**/*
📄 CodeRabbit inference engine (.cursor/rules/overview.mdc)
Document all components and features in the documentation website (apps/www/)
Files:
apps/www/src/components/mobile-nav/sheet.tsxapps/www/src/layouts/root-layout.astroapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/index.astroapps/www/src/pages/examples/[...slug].astro
apps/www/src/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
The main documentation website source code must be located under apps/www/src/
Files:
apps/www/src/components/mobile-nav/sheet.tsxapps/www/src/layouts/root-layout.astroapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/index.astroapps/www/src/pages/examples/[...slug].astro
apps/www/**
📄 CodeRabbit inference engine (AGENTS.md)
Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Files:
apps/www/src/components/mobile-nav/sheet.tsxapps/www/src/layouts/root-layout.astroapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/index.astroapps/www/src/pages/examples/[...slug].astro
🧠 Learnings (2)
📚 Learning: 2025-08-03T17:50:06.904Z
Learnt from: CR
PR: shipbase/ui#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-08-03T17:50:06.904Z
Learning: Applies to apps/www/astro.config.ts : Astro configuration for the documentation website must be defined in apps/www/astro.config.ts
Applied to files:
apps/www/src/layouts/root-layout.astroapps/www/src/pages/docs/components/[component].astroapps/www/src/pages/docs/[...slug].astroapps/www/src/config/site.tsapps/www/src/pages/index.astro
📚 Learning: 2025-08-30T18:58:30.749Z
Learnt from: CR
PR: shipbase/ui#0
File: AGENTS.md:0-0
Timestamp: 2025-08-30T18:58:30.749Z
Learning: Applies to apps/www/** : Host the documentation site code in apps/www (Astro/MDX/Tailwind v4)
Applied to files:
apps/www/src/pages/docs/components/[component].astroapps/www/src/pages/index.astro
🧬 Code graph analysis (2)
apps/www/src/components/mobile-nav/sheet.tsx (2)
examples/next/components/ui/sheet.tsx (5)
props(77-83)props(85-93)props(98-105)props(24-33)props(42-70)packages/react/src/components/ui/sheet.tsx (3)
props(91-98)props(114-121)props(51-83)
apps/www/src/pages/examples/[...slug].astro (1)
examples/next/app/layout.tsx (1)
RootLayout(20-34)
🔇 Additional comments (4)
apps/www/src/pages/index.astro (2)
21-38: LGTM: metadata wiring is clear and consistentMetadata shape (title/description/OpenGraph/Twitter) looks coherent with the new RootLayout API.
41-48: Nice cleanupConverting separators to self-closing tags improves readability without behavior changes.
apps/www/src/config/site.ts (1)
3-3: Domain consistency verified. Astro’ssitefield sourcessiteConfig.url(set to https://ui.shipbase.xyz in src/config/site.ts) and no hard-coded origin URLs were found in robots.txt or sitemap generation.apps/www/src/layouts/root-layout.astro (1)
43-43: LGTM: Dynamic, metadata-driven title.
| <meta name="author" content={siteConfig.author} /> | ||
| <meta name="creator" content={siteConfig.creator} /> | ||
| <meta name="theme-color" content={META_THEME_COLORS.light} /> | ||
| <meta name="robots" content="index,follow" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Allow per-page robots override (avoid indexing 404s).
- <meta name="robots" content="index,follow" />
+ <meta name="robots" content={robots} />Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/www/src/layouts/root-layout.astro around line 55, the robots meta tag is
hard-coded to "index,follow" which prevents per-page overrides (causing 404s and
other pages to be indexed). Update the layout to read a robots value passed from
the page (e.g., frontmatter or Astro.props) and use that value for the meta tag,
falling back to "index,follow" when not provided; also update the 404 page to
set robots: "noindex,nofollow" (or ensure pages can set their own robots
frontmatter) so pages can opt out of indexing.
| <meta name="theme-color" content={META_THEME_COLORS.light} /> | ||
| <meta name="robots" content="index,follow" /> | ||
|
|
||
| <meta property="og:locale" content="en_US" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Confirm /api/og route exists and is used across pages.
To prevent 404s and broken previews, ensure an API route at apps/www/src/pages/api/og.* and that pages pass absolute image URLs.
🏁 Script executed:
#!/bin/bash
# Verify OG API route and references
echo "OG API route files:"
fd -a --glob "apps/www/src/pages/api/og*.*" || true
echo -e "\nReferences to /api/og vs /og:"
rg -n "/api/og|\\b/og\\b" apps/www | sed -n '1,200p'Length of output: 220
Add missing OG API route and update references
No file found at apps/www/src/pages/api/og.*, and no occurrences of /api/og in the codebase—this will break social previews. Create the OG endpoint under src/pages/api/og.(ts|js) and update your meta tags (e.g. in apps/www/src/layouts/root-layout.astro) to use /api/og with absolute image URLs.
🤖 Prompt for AI Agents
apps/www/src/layouts/root-layout.astro around line 57: the project references an
OG image endpoint but there is no API route at apps/www/src/pages/api/og.(ts|js)
and meta tags currently won't resolve; create a new API route file
apps/www/src/pages/api/og.ts (or .js) that generates/returns the Open Graph
image (PNG/WebP) or proxies a static image and responds with correct
content-type and cache headers, and then update the meta tags in
root-layout.astro to point the og:image (and open graph/twitter image tags) to
the absolute URL of that endpoint (e.g. https://your-domain.com/api/og) so
social previews can fetch the image correctly.
| const { entry } = Astro.props | ||
| const { Content, headings } = await render(entry) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type Astro.props for docs overview pages
Add a Props interface to satisfy type checking.
-import { getCollection, render } from "astro:content"
+import { getCollection, render } from "astro:content"
+import type { CollectionEntry } from "astro:content"
@@
-const { entry } = Astro.props
+export interface DocsPageProps {
+ entry: CollectionEntry<"overview">
+}
+const { entry } = Astro.props as DocsPageProps📝 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.
| const { entry } = Astro.props | |
| const { Content, headings } = await render(entry) | |
| ++ b/apps/www/src/pages/docs/[...slug].astro | |
| @@ | |
| import { getCollection, render } from "astro:content" | |
| import type { CollectionEntry } from "astro:content" | |
| @@ | |
| export interface DocsPageProps { | |
| entry: CollectionEntry<"overview"> | |
| } | |
| const { entry } = Astro.props as DocsPageProps | |
| const { Content, headings } = await render(entry) |
🤖 Prompt for AI Agents
In apps/www/src/pages/docs/[...slug].astro around lines 19-20, Astro.props is
untyped; add a Props interface (e.g. interface Props { entry: /* specific entry
type from your render/mdx pipeline, e.g. RenderResult | MarkdownInstance */ })
and then assert or annotate Astro.props with that interface (for example: const
{ entry } = Astro.props as Props) so type checking succeeds; pick the concrete
type used by your render(entry) function (import or reference the existing type)
and use that in the Props definition.
| const { entry } = Astro.props | ||
| const { Content, headings } = await render(entry) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type Astro.props for component docs pages
Define a Props interface for type safety and to pass “astro check”.
-import { getCollection, render } from "astro:content"
+import { getCollection, render } from "astro:content"
+import type { CollectionEntry } from "astro:content"
@@
-const { entry } = Astro.props
+export interface ComponentDocsPageProps {
+ entry: CollectionEntry<"components">
+}
+const { entry } = Astro.props as ComponentDocsPageProps📝 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.
| const { entry } = Astro.props | |
| const { Content, headings } = await render(entry) | |
| --- apps/www/src/pages/docs/components/[component].astro | |
| @@ | |
| import { getCollection, render } from "astro:content" | |
| import type { CollectionEntry } from "astro:content" | |
| @@ | |
| export interface ComponentDocsPageProps { | |
| entry: CollectionEntry<"components"> | |
| } | |
| const { entry } = Astro.props as ComponentDocsPageProps |
🤖 Prompt for AI Agents
In apps/www/src/pages/docs/components/[component].astro around lines 26-27,
Astro.props is currently untyped; define a Props interface (e.g. interface Props
{ entry: <appropriate type> }) and then cast/annotate Astro.props with that
interface (for example: const { entry } = Astro.props as Props or const Props =
Astro.props as Props and destructure), so the component has type-safe access to
entry and passes astro check; pick the concrete type for entry from your
collection/renderer types (or temporarily use a specific union/any if unknown)
and update any downstream usages accordingly.
| const url = Astro.url | ||
| const imageURL = new URL( | ||
| `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`, | ||
| url.origin | ||
| ).toString() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Confirm OG endpoint path matches implementation
PR summary mentions /api/og, but pages build URLs for /og. Ensure the route path is consistent across code and docs.
🏁 Script executed:
#!/usr/bin/env bash
# Verify which OG route exists
fd -t f 'og' apps/www/src/pages | sed -n '1,80p'
rg -n 'export const GET' -g 'apps/www/src/pages/**/og/**'
rg -n '/api/og|/og\\?' -g 'apps/www/src/**'Length of output: 842
Update PR summary to reference the /og endpoint, not /api/og
The code in apps/www/src/pages/og/index.ts defines the GET handler at /og, and all page templates build URLs against /og (not /api/og), so the PR description should be updated for consistency.
🤖 Prompt for AI Agents
In apps/www/src/pages/examples/[...slug].astro around lines 17 to 21 the code
builds image URLs against the /og endpoint (e.g.,
`/og?title=...&description=...`) so the PR summary should reference `/og` rather
than `/api/og`; update the PR description (and any related changelog or docs in
the PR body) to mention the /og endpoint exactly, ensuring consistency with the
GET handler defined in apps/www/src/pages/og/index.ts and with how templates
construct URLs.
Summary
Test plan
/api/ogendpoint/robots.txt/sitemap.xml🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Enhancements
Chores