Skip to content

Conversation

@iamdin
Copy link
Member

@iamdin iamdin commented Sep 2, 2025

Summary

  • 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 and improved performance
  • Add proper meta tags including Open Graph, Twitter cards, and schema markup

Test plan

  • Verify OG images render correctly at /api/og endpoint
  • Check robots.txt is accessible at /robots.txt
  • Validate sitemap.xml generation at /sitemap.xml
  • Test 404 page functionality
  • Confirm structured data validates with Google's Rich Results Test
  • Verify meta tags are properly rendered in page source

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Server-rendered Open Graph image endpoint, sitemap-index, robots.txt route, reusable OpenGraph image component, and custom 404 page.
  • Enhancements

    • Site-wide metadata overhaul (OG/Twitter/canonical/theme-color/robots), metadata-driven layouts with dynamic OG images, updated site branding (URL, title, description, images, theme colors), and prerendering for docs/examples.
  • Chores

    • Updated preview/upload scripts, added/removed dependencies for OG/image tooling, pinned image lib version, and updated hosting compatibility date.

- 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>
@changeset-bot
Copy link

changeset-bot bot commented Sep 2, 2025

⚠️ No Changeset found

Latest commit: 0712129

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds 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

Cohort / File(s) Summary of changes
Astro config & deps
apps/www/astro.config.ts, apps/www/package.json, pnpm-workspace.yaml
Set site to siteConfig.url, add output: "server", include sitemap() integration, adjust Cloudflare adapter to cloudflare({ imageService: "compile" }), add vite.ssr.external node built-ins; update scripts (previewwrangler dev, add upload); add deps @astrojs/sitemap, @resvg/resvg-wasm, satori, tiny-invariant, yoga-wasm-web; remove sharp from app deps; pin sharp in workspace.
Site config & manifest
apps/www/src/config/site.ts, apps/www/public/site.webmanifest
Update siteConfig (url → https://ui.shipbase.xyz, add author/creator, set ogImage/twitterImage, update title/description, update twitter link) and add META_THEME_COLORS; pretty-print and change manifest values (lowercase name/short_name, theme/background colors).
Root layout & metadata flow
apps/www/src/layouts/root-layout.astro
Export Props interface; accept metadata prop and derive metadata from Astro.props; render dynamic head meta (title, description, keywords, author/creator, theme-color, robots, Open Graph, Twitter, canonical); change sitemap path to /sitemap-index.xml; remove manifest link; add inline theme/layout script and load framework/package-manager sync scripts.
Pages supplying metadata
apps/www/src/pages/index.astro, apps/www/src/pages/examples/index.astro, apps/www/src/pages/examples/[...slug].astro, apps/www/src/pages/docs/[...slug].astro, apps/www/src/pages/docs/components/[component].astro, apps/www/src/pages/404.astro
Import siteConfig where needed, export prerender = true on several pages, compute dynamic OG image URLs via /og?title=...&description=..., and build/pass consolidated metadata objects to RootLayout (title, description, openGraph, twitter, url). Minor markup fixes (self-closing sections).
New server routes
apps/www/src/pages/og/index.ts, apps/www/src/pages/robots.txt.ts
Add /og APIRoute that generates SVG via Satori WASM and rasterizes to PNG with Resvg WASM (1200×630), initializes fonts/WASM on demand, reads query params with siteConfig fallbacks, returns image/png with long-term cache headers (prerender = false); add /robots.txt APIRoute that resolves sitemap-index.xml.
Open Graph component
apps/www/src/components/open-graph.tsx
Add OpenGraph React component and OpenGraphProps to render a stylized OG layout (title, description, logo) used by the OG generator.
UI text & small component edits
apps/www/src/components/landing/cta.astro, apps/www/src/components/mobile-nav/sheet.tsx
Remove unused Input import; change CTA heading to “Shadcn UI Components for”; swap static paragraph to siteConfig.description; add z-index to left sheet (z-[50] pr-0).
Utilities
packages/lib/utils/url.ts
Add `export function removeTrailingSlash(input: string
Tooling & workspace
packages/react/package.json, apps/www/wrangler.jsonc
Add devDependency typescript: "catalog:" in packages/react; update compatibility_date in apps/www/wrangler.jsonc to "2025-08-15".

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
Loading
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
Loading
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>)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I twitch my whiskers, map in paw,
Sitemaps hum and OGs I draw,
SVGs I sketch, PNGs I bake,
Trailing slashes vanish in my wake.
Hop along — the site’s awake! 🐇✨


📜 Recent 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 87de8b7 and 0712129.

📒 Files selected for processing (1)
  • apps/www/src/components/open-graph.tsx (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-seo

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

Copy link

@coderabbitai coderabbitai bot left a 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?.node

Then prefer Resvg path on Workers.

apps/www/src/components/landing/cta.astro (1)

31-31: Add noopener for external link security.

target="_blank" should include rel="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: Replace entry.id with entry.slug and remove the unnecessary .flat(1)
getCollection() returns a flat array of entries; for content collections entry.id includes file extensions, whereas entry.slug is 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, "&amp;")
+    .replace(/</g, "&lt;")
+    .replace(/>/g, "&gt;")
+    .replace(/"/g, "&quot;")
+    .replace(/'/g, "&apos;")
+
 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 keep links.twitter for the profile URL; use the handle for twitter:creator.


1-1: Constant naming guideline (nit).

Guidelines say constants use SCREAMING_SNAKE_CASE; siteConfig is 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‑framework
apps/www/src/pages/docs/components/[component].astro (1)

31-39: Provide a safe fallback for missing frontmatter.

If a doc lacks description, fall back to siteConfig.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 noindex on 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 entry is 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.description is 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/promises and sharp won’t run on Workers/edge. If you need Worker/edge compatibility, switch to resvg-wasm and import assets via new 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 public and 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 with process.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/description length 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.

📥 Commits

Reviewing files that changed from the base of the PR and between a2b9745 and 691409d.

⛔ Files ignored due to path filters (8)
  • apps/www/public/fonts/Geist-Regular.ttf is excluded by !**/*.ttf
  • apps/www/public/fonts/Geist-SemiBold.ttf is excluded by !**/*.ttf
  • apps/www/public/fonts/GeistMono-Regular.ttf is excluded by !**/*.ttf
  • apps/www/public/logo-dark.png is excluded by !**/*.png
  • apps/www/public/logo-dark.svg is excluded by !**/*.svg
  • apps/www/public/logo.png is excluded by !**/*.png
  • apps/www/public/og-image.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is 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.ts
  • apps/www/astro.config.ts
  • apps/www/src/pages/og/index.ts
  • apps/www/src/pages/sitemap.xml.ts
  • apps/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.ts
  • apps/www/astro.config.ts
  • apps/www/package.json
  • apps/www/src/pages/og/index.ts
  • apps/www/src/pages/sitemap.xml.ts
  • apps/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.ts
  • apps/www/astro.config.ts
  • apps/www/src/pages/404.astro
  • apps/www/package.json
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/components/landing/cta.astro
  • apps/www/src/pages/index.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/src/pages/og/index.ts
  • apps/www/src/pages/sitemap.xml.ts
  • apps/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.ts
  • apps/www/src/pages/404.astro
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/components/landing/cta.astro
  • apps/www/src/pages/index.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/src/pages/og/index.ts
  • apps/www/src/pages/sitemap.xml.ts
  • apps/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.ts
  • apps/www/astro.config.ts
  • apps/www/src/pages/404.astro
  • apps/www/package.json
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/components/landing/cta.astro
  • apps/www/src/pages/index.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/src/pages/og/index.ts
  • apps/www/src/pages/sitemap.xml.ts
  • apps/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.ts
  • apps/www/astro.config.ts
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/src/pages/sitemap.xml.ts
  • apps/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 verified

apps/www/src/config/site.ts (2)

3-3: Ensure Astro’s site and URL generation use the new domain
– In apps/www/astro.config.ts, import the url from apps/www/src/config/site.ts and set site: url to https://ui.shipbase.xyz.
– In apps/www/src/pages/sitemap.xml.ts and robots.txt.ts, switch URL construction to use Astro.site (or your siteConfig.url) to guarantee the same origin and apply a uniform trailing-slash policy.


4-6: OG endpoint and meta setup verified

The /api/og route returns a 200 response with Content-Type: image/png and Cache-Control: s-maxage=1, stale-while-revalidate=59. In root-layout.astro, both og:image and twitter:image are built as absolute URLs via Astro.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 dynamic fontSize.


299-299: PNG generation path is correct.

satori -> svg -> sharp.png() is a reliable Node pipeline.

Comment on lines 21 to 25
const siteName = siteConfig.name
const baseUrl = Astro.site?.toString().replace(/\/$/, "") ?? ""
const path = Astro.url?.pathname ?? "/"
const canonical = `${baseUrl}${path}`
Copy link

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.

Suggested change
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.

Comment on lines 29 to 35
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}`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines 31 to 35
const ogImageAbs = ogImage.startsWith("http") ? ogImage : `${baseUrl}${ogImage}`
const twitterImage = siteConfig.twitterImage || ogImage
const twitterImageAbs = twitterImage.startsWith("http")
? twitterImage
: `${baseUrl}${twitterImage}`
Copy link

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.

Suggested change
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} />
Copy link

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/www

Length 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.

Comment on lines 3 to 8
export const GET: APIRoute = async ({ site }) => {
const base = site?.toString().replace(/\/$/, "") ?? ""
const body = `User-agent: *
Allow: /f
Sitemap: ${base}/sitemap.xml\n`

Copy link

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.

Suggested change
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.

Comment on lines 5 to 7
const body = `User-agent: *
Allow: /f
Sitemap: ${base}/sitemap.xml\n`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines 25 to 27
export const GET: APIRoute = async ({ site }) => {
const base = site?.toString().replace(/\/$/, "") ?? ""
const now = new Date().toISOString()
Copy link

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.

Suggested change
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

Copy link

@coderabbitai coderabbitai bot left a 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 /og route

  • apps/www: install deps (pnpm install) then run pnpm run typecheck to surface any errors.
  • apps/www/src/config/site.ts references /og but no apps/www/src/pages/og* exists; create the OG page (e.g. src/pages/og.tsx or .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 // or example.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.

📥 Commits

Reviewing files that changed from the base of the PR and between 691409d and 5171b47.

⛔ Files ignored due to path filters (3)
  • apps/www/public/og-image.png is excluded by !**/*.png
  • apps/www/public/og-image.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is 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.ts
  • apps/www/astro.config.ts
  • apps/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.ts
  • packages/react/package.json
  • apps/www/package.json
  • apps/www/astro.config.ts
  • apps/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.webmanifest
  • apps/www/src/pages/examples/index.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/package.json
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/astro.config.ts
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/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.webmanifest
  • apps/www/src/pages/examples/index.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/package.json
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/astro.config.ts
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/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.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/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.astro
  • apps/www/src/pages/examples/[...slug].astro
  • apps/www/package.json
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/www/astro.config.ts
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/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].astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/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. Run pnpm 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 .tsx content 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.xml if 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" /> -->

Comment on lines 15 to 16
const { entry } = Astro.props
---
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

Copy link

@coderabbitai coderabbitai bot left a 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.href

Reduce 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 type

Use 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 5171b47 and 40d9cc4.

⛔ Files ignored due to path filters (1)
  • apps/www/public/og-image.png is 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].astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/layouts/root-layout.astro
  • 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/docs/[...slug].astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/layouts/root-layout.astro
  • 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/docs/[...slug].astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/layouts/root-layout.astro
  • apps/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.ts
  • 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/config/site.ts
  • apps/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].astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/docs/components/[component].astro
  • apps/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].astro
  • apps/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 maps pages/og/index.ts to /og by default (nextjs.org); there is no apps/www/src/pages/api/og/index.ts nor any references to /api/og in 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 fallbacks

Import looks right and aligns with centralizing branding/SEO defaults.


10-10: Verify prerender + OG endpoint compatibility

If 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/og route in the codebase—please confirm the correct OG-generation endpoint.
apps/www/src/pages/docs/components/[component].astro (2)

6-6: LGTM: siteConfig import

Consistent with project-wide SEO defaults.


10-10: Confirm prerender with dynamic OG route

Static prerender is fine; just ensure your deployed target serves the OG endpoint (likely /api/og) at runtime.

Comment on lines 28 to 31
const { Content, headings } = await render(entry)
const ogImage = `/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`
---
Copy link

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.ts

Length of output: 2248


Use fallback description and build an absolute OG URL with the correct /og endpoint

  • Replace entry.data.description with entry.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 canonical variable.
-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.

Suggested change
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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

Copy link

@coderabbitai coderabbitai bot left a 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 40d9cc4 and 343dffb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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.ts
  • apps/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.ts
  • apps/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.ts
  • apps/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
The apps/www/astro.config.ts file imports @astrojs/sitemap, includes sitemap() in integrations, and sets site: 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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 2, 2025

Copy link

@coderabbitai coderabbitai bot left a 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 logo

Using 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 allocation

image.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 images

Current 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec15ad and bc1cec4.

📒 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/og remain in apps/www; dynamic OG images use the /og?… route and the static fallback is /og-image.png via siteConfig.
  • Update any external docs (README, test plan) still referencing /api/og to /og or /og-image.png as appropriate.

Comment on lines 73 to 79
const { searchParams } = new URL(url)

const title = searchParams.get("title") ?? siteConfig.name
const description =
searchParams.get("description") ?? siteConfig.description

const fonts = await loadFonts()
Copy link

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.

Suggested change
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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 3, 2025

Copy link

@coderabbitai coderabbitai bot left a 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 external resources.

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 entry is 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 Props

Optional 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: Make site env-overridable to avoid wrong canonical/sitemap URLs in previews.

Using siteConfig.url is 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: Confirm imageService: "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 sharp imports remain.


19-23: Tune sitemap to exclude API/404/OG routes.

By default, sitemap may pick up unwanted routes. Exclude endpoints like /api/*, /_routes, and 404 (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 targeting webworker.

Listing node:* modules under vite.ssr.external leaves bare node: 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.

📥 Commits

Reviewing files that changed from the base of the PR and between bc1cec4 and 523ec49.

📒 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.tsx
  • apps/www/src/pages/og/index.ts
  • apps/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.tsx
  • apps/www/src/pages/og/index.ts
  • apps/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.tsx
  • apps/www/src/pages/og/index.ts
  • apps/www/src/pages/examples/[...slug].astro
  • apps/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.tsx
  • apps/www/src/pages/og/index.ts
  • apps/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.tsx
  • apps/www/src/pages/og/index.ts
  • apps/www/src/pages/examples/[...slug].astro
  • apps/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].astro
  • apps/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/sitemap is appropriate, especially with site set 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/og aren’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 /og endpoint.

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
@github-actions
Copy link
Contributor

github-actions bot commented Sep 3, 2025

Copy link

@coderabbitai coderabbitai bot left a 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, initResvgPromise isn’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 any

Replace 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 523ec49 and d33f88d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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.ts
  • 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/pages/og/index.ts
  • apps/www/src/components/open-graph.tsx
  • apps/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.ts
  • apps/www/src/components/open-graph.tsx
  • apps/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/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.ts
  • apps/www/src/components/open-graph.tsx
  • apps/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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 3, 2025

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>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 3, 2025

@iamdin iamdin merged commit 93928cc into next Sep 3, 2025
2 of 3 checks passed
@iamdin iamdin deleted the feat-seo branch September 3, 2025 16:07
Copy link

@coderabbitai coderabbitai bot left a 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 ExamplesPageProps
apps/www/src/pages/docs/components/[component].astro (1)

29-33: Guard description and build OG URL against a stable origin

Same 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.ogEndpoint when 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 scale

Use 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 origin

Fallback 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 metadata

Including 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 description

Mirror 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 images

Small 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 docs

Component documentation pages typically map better to “article” than “website”.

-      type: "website",
+      type: "article",

46-51: Add alt text to OG/Twitter images

Improve 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:creator expects a handle (e.g., @user); keep creator as 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.twitterHandle for twitter: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 const
apps/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 RootLayoutProps

And 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.

📥 Commits

Reviewing files that changed from the base of the PR and between d33f88d and 87de8b7.

📒 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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/index.astro
  • apps/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.tsx
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/index.astro
  • apps/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.tsx
  • apps/www/src/layouts/root-layout.astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/config/site.ts
  • apps/www/src/pages/index.astro
  • apps/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.astro
  • apps/www/src/pages/docs/components/[component].astro
  • apps/www/src/pages/docs/[...slug].astro
  • apps/www/src/config/site.ts
  • apps/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].astro
  • apps/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 consistent

Metadata shape (title/description/OpenGraph/Twitter) looks coherent with the new RootLayout API.


41-48: Nice cleanup

Converting separators to self-closing tags improves readability without behavior changes.

apps/www/src/config/site.ts (1)

3-3: Domain consistency verified. Astro’s site field sources siteConfig.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" />
Copy link

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" />
Copy link

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.

Comment on lines 19 to 20
const { entry } = Astro.props
const { Content, headings } = await render(entry)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines 26 to 27
const { entry } = Astro.props
const { Content, headings } = await render(entry)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +17 to +21
const url = Astro.url
const imageURL = new URL(
`/og?title=${encodeURIComponent(entry.data.title)}&description=${encodeURIComponent(entry.data.description)}`,
url.origin
).toString()
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants