Skip to content

feat: add Next.js marketing + documentation website#19

Open
GreenKeewi wants to merge 22 commits into
raroque:mainfrom
GreenKeewi:feat/marketing-website
Open

feat: add Next.js marketing + documentation website#19
GreenKeewi wants to merge 22 commits into
raroque:mainfrom
GreenKeewi:feat/marketing-website

Conversation

@GreenKeewi
Copy link
Copy Markdown

Summary

  • Adds a complete 2-page Next.js 14 marketing website in website/ — landing page (/) and combined docs page (/docs)
  • Landing page includes: Hero with animated boop.gif mascot, Mermaid architecture diagram, 12-card features grid, 5-step quickstart with copy buttons, and prerequisites table with affiliate links
  • Docs page fetches all 5 markdown files (README, ARCHITECTURE, INTEGRATIONS, CONTRIBUTING, CHANGELOG) from GitHub at build time via ISR, rendered with rehype-pretty-code syntax highlighting and a fixed sidebar with IntersectionObserver active-section tracking
  • Full SEO: metadata, sitemap, robots.txt, OG image via edge route

Tech stack

Next.js 14 App Router · TypeScript · Tailwind CSS v3 (custom dark/orange theme) · unified/remark/rehype · Mermaid.js · lucide-react · next/font (Inter + JetBrains Mono)

Test plan

  • cd website && npm run build completes with zero errors
  • npm run dev → verify landing page at http://localhost:3000
  • Hero animated gif plays, "Quick start ↓" button scrolls to quickstart section
  • "Quick start" in navbar jumps to the section
  • Mermaid diagram renders (dark theme, responsive)
  • Copy buttons work on quickstart steps
  • /docs — all 5 sections render with syntax-highlighted code blocks
  • Images in docs (boop.gif, screenshots) load from GitHub raw URLs
  • Sidebar highlights active section on scroll; mobile tab bar works
  • http://localhost:3000/og returns 1200×630 OG image
  • http://localhost:3000/sitemap.xml and /robots.txt return correctly

🤖 Generated with Claude Code

GreenKeewi and others added 22 commits April 27, 2026 18:14
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Copy boop.gif to public/assets and use animated GIF in Hero mascot and H1 inline emoji
- Post-process markdown HTML to rewrite relative asset URLs to GitHub raw URLs
- Add Quick start CTA button in Hero, id/scroll-mt on QuickstartBlock, and Quick start links in desktop and mobile Navbar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 27, 2026 22:46
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR adds a complete Next.js 14 marketing and documentation website under website/ with a landing page, a docs page fetching markdown from GitHub via ISR, an OG image edge route, sitemap, and robots.txt.

  • website/lib/docs.ts: The unified markdown pipeline uses allowDangerousHtml: true in both remarkRehype and rehypeStringify with no sanitization step, making the docs page vulnerable to XSS if any fetched markdown file contains raw HTML with scripts or event handlers. rehype-sanitize should be added to the pipeline.
  • website/lib/docs.ts: Promise.all in getAllDocs is not wrapped in a try/catch — a network throw (as opposed to a non-2xx response) will propagate unhandled and crash the docs page on a cold ISR start.

Confidence Score: 3/5

Hold for the XSS and unhandled-rejection issues in the docs pipeline before merging.

Two P1 findings both in website/lib/docs.ts: raw HTML pass-through with no sanitization (allowDangerousHtml: true + no rehypeSanitize) creates a real XSS vector, and the unhandled Promise.all rejection can crash the docs page on a cold start. Multiple P1s lower the score below the 4/5 ceiling.

website/lib/docs.ts needs both a rehypeSanitize step and a try/catch around each fetch in Promise.all.

Security Review

  • XSS via raw HTML pass-through (website/lib/docs.ts): The unified pipeline uses allowDangerousHtml: true in both remarkRehype and rehypeStringify with no sanitization step. Raw HTML embedded in any of the five fetched markdown files passes directly into dangerouslySetInnerHTML, making the docs page vulnerable to XSS if any markdown file is tampered with or contains unsafe HTML. Adding rehype-sanitize would close this gap.

Important Files Changed

Filename Overview
website/lib/docs.ts Fetches and renders 5 markdown files from GitHub at build time via ISR; raw HTML pass-through (allowDangerousHtml: true) and missing error handling around Promise.all are two actionable issues.
website/components/DocSection.tsx Renders pre-processed HTML with dangerouslySetInnerHTML; safe only if the upstream pipeline is sanitized (currently it is not).
website/components/DocsLayout.tsx Client-side docs layout with IntersectionObserver sidebar tracking and mobile tab bar; cleanup in useEffect is correct and scroll behavior is solid.
website/components/Hero.tsx Landing hero section; hardcoded star/fork counts will go stale; no runtime issues.
website/components/MermaidDiagram.tsx Lazy-loads Mermaid, renders a hardcoded diagram, sets innerHTML from Mermaid's own SVG output (not external input); fallback on error is clean.
website/app/og/route.tsx Edge-runtime OG image via next/og with static content; correct dimensions (1200×630).
website/app/page.tsx Landing page server component; correctly wraps content in <main id="main-content"> enabling the skip-to-content link.
website/components/CopyButton.tsx Clipboard copy with execCommand fallback; checks typeof window guard appropriately.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Next.js (Edge)
    participant Next.js (Server)
    participant GitHub Raw

    Browser->>Next.js (Edge): GET /og
    Next.js (Edge)-->>Browser: 1200x630 ImageResponse

    Browser->>Next.js (Server): GET /docs
    Next.js (Server)->>GitHub Raw: fetch README.md (revalidate: 3600)
    Next.js (Server)->>GitHub Raw: fetch ARCHITECTURE.md (revalidate: 3600)
    Next.js (Server)->>GitHub Raw: fetch INTEGRATIONS.md (revalidate: 3600)
    Next.js (Server)->>GitHub Raw: fetch CONTRIBUTING.md (revalidate: 3600)
    Next.js (Server)->>GitHub Raw: fetch CHANGELOG.md (revalidate: 3600)
    GitHub Raw-->>Next.js (Server): markdown text x5
    Note over Next.js (Server): unified pipeline (remark to rehype to rehypePrettyCode) allowDangerousHtml:true, no rehypeSanitize
    Next.js (Server)-->>Browser: HTML (dangerouslySetInnerHTML)
    Browser->>Browser: IntersectionObserver sidebar tracking
Loading

Reviews (1): Last reviewed commit: "docs: add website implementation plan an..." | Re-trigger Greptile

Comment thread website/lib/docs.ts
Comment on lines +26 to +31
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypePrettyCode as any, {
theme: 'github-dark',
keepBackground: true,
})
.use(rehypeStringify, { allowDangerousHtml: true })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Raw HTML pass-through enables XSS

Both remarkRehype({ allowDangerousHtml: true }) and rehypeStringify({ allowDangerousHtml: true }) are set, which allows any raw HTML embedded in the fetched markdown files to flow through the pipeline completely unsanitized and land in dangerouslySetInnerHTML. If any of the five markdown files on main ever contains a <script> tag or an event-handler attribute (via a compromised commit, a drive-by PR merge, or branch tampering), it will execute in every visitor's browser.

Adding rehype-sanitize between the code-highlighting step and rehypeStringify is the standard fix and has no visual impact on the rendered output.

import rehypeSanitize from 'rehype-sanitize'

// in parseMarkdown:
.use(rehypePrettyCode as any, { theme: 'github-dark', keepBackground: true })
.use(rehypeSanitize)   // <-- add this
.use(rehypeStringify, { allowDangerousHtml: true })

Comment thread website/lib/docs.ts
Comment on lines +42 to +51
export async function getAllDocs(): Promise<DocFile[]> {
const results = await Promise.all(
DOC_FILES.map(async ({ id, label, url }) => {
const res = await fetch(url, { next: { revalidate: 3600 } })
const markdown = res.ok ? await res.text() : `# ${label}\n\n*Could not load this document.*`
const html = await parseMarkdown(markdown)
return { id, label, html }
})
)
return results
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Network error crashes docs page on cold build

Promise.all is not wrapped in a try/catch. If any fetch call throws (e.g., DNS failure, GitHub rate-limit at build time, or transient network hiccup on a cold ISR start), the entire getAllDocs call rejects and the docs page shows a Next.js error boundary rather than the graceful per-section fallback that res.ok already handles for non-2xx responses.

export async function getAllDocs(): Promise<DocFile[]> {
  const results = await Promise.all(
    DOC_FILES.map(async ({ id, label, url }) => {
      try {
        const res = await fetch(url, { next: { revalidate: 3600 } })
        const markdown = res.ok ? await res.text() : `# ${label}\n\n*Could not load this document.*`
        const html = await parseMarkdown(markdown)
        return { id, label, html }
      } catch {
        const html = await parseMarkdown(`# ${label}\n\n*Could not load this document.*`)
        return { id, label, html }
      }
    })
  )
  return results
}

Comment on lines +86 to +98
{[
'43 stars',
'11 forks',
'MIT License',
'Built on Claude Agent SDK',
].map((stat) => (
<span
key={stat}
className="px-3 py-1 rounded-full text-xs text-text-muted border border-border bg-bg-card"
>
{stat}
</span>
))}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Hardcoded star/fork counts will go stale immediately

'43 stars' and '11 forks' are static strings baked into the bundle. As soon as the PR merges, these counts will drift from reality. Consider either fetching live data from the GitHub API (https://api.github.com/repos/raroque/boop-agent) with ISR, or removing the count badges altogether and linking the pills to the GitHub repo so the real numbers are always one click away.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a standalone Next.js 14 marketing + documentation site under website/, including a landing page and a /docs page that renders project markdown with syntax highlighting, plus SEO endpoints (sitemap/robots/OG).

Changes:

  • Scaffolded a new Next.js 14 (App Router) site in website/ with Tailwind, TypeScript, and build scripts.
  • Implemented landing-page sections (hero, architecture/mermaid, features, quickstart w/ copy buttons, prerequisites) and shared layout components.
  • Implemented /docs rendering via GitHub raw markdown fetch + unified/rehype pipeline, with a client sidebar using IntersectionObserver, and added SEO routes/metadata.

Reviewed changes

Copilot reviewed 25 out of 30 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
website/tsconfig.json TypeScript configuration for the Next.js site.
website/tailwind.config.ts Tailwind theme and content scanning paths.
website/postcss.config.js PostCSS setup for Tailwind + autoprefixer.
website/package.json Website dependencies and scripts.
website/next.config.mjs Next config for remote images and package import optimization.
website/next-env.d.ts Next.js TypeScript environment declarations.
website/lib/docs.ts Fetches and converts GitHub markdown to HTML for /docs.
website/components/QuickstartBlock.tsx Quickstart steps UI with copy-to-clipboard actions.
website/components/PrerequisitesTable.tsx Prerequisites table with external links.
website/components/Navbar.tsx Responsive nav with mobile menu and top links.
website/components/MermaidDiagram.tsx Client-side Mermaid render with fallback text diagram.
website/components/Hero.tsx Landing hero section with CTAs and mascot imagery.
website/components/Footer.tsx Site footer with links and attribution.
website/components/FeaturesGrid.tsx Features grid of product capabilities.
website/components/DocsLayout.tsx Docs page layout with active-section tracking + mobile tabs.
website/components/DocSection.tsx Renders each fetched doc section into the page.
website/components/CopyButton.tsx Client copy-to-clipboard button used in quickstart.
website/components/ArchitectureBlock.tsx Architecture section combining Mermaid diagram + component cards.
website/app/sitemap.ts Sitemap generation for / and /docs.
website/app/robots.ts Robots.txt generation referencing sitemap.
website/app/page.tsx Landing page composition.
website/app/og/route.tsx Edge OG image generation route.
website/app/layout.tsx Root layout, fonts, and global metadata.
website/app/globals.css Global styles and custom prose styles for rendered docs.
website/app/docs/page.tsx /docs page assembly and ISR configuration.
docs/superpowers/plans/2026-04-27-boop-website.md Implementation plan documentation for building the site.
.gitignore Ignores Next build output and dependencies under website/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread website/lib/docs.ts
Comment on lines +26 to +27
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypePrettyCode as any, {
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The markdown pipeline enables allowDangerousHtml, which preserves raw HTML from the fetched markdown and can lead to XSS when combined with dangerouslySetInnerHTML on the docs page. Consider stripping raw HTML or sanitizing the output (e.g., rehype-sanitize with an allowlist for img, a, etc.).

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +22
<div
className="prose-custom"
dangerouslySetInnerHTML={{ __html: doc.html }}
/>
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

This renders doc.html via dangerouslySetInnerHTML. Because the HTML is produced from remotely fetched markdown and the parser currently preserves raw HTML, this can allow XSS in the docs page. Please ensure doc.html is sanitized/escaped before injection (or change the markdown pipeline to strip/sanitize raw HTML).

Copilot uses AI. Check for mistakes.

const scrollTo = (id: string) => {
const el = document.getElementById(id)
el?.scrollIntoView({ behavior: 'smooth', block: 'start' })
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

scrollIntoView({ behavior: 'smooth' }) forces smooth scrolling even when the user has prefers-reduced-motion: reduce. Consider honoring reduced-motion by switching to behavior: 'auto' when that media query matches.

Suggested change
el?.scrollIntoView({ behavior: 'smooth', block: 'start' })
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches
el?.scrollIntoView({
behavior: prefersReducedMotion ? 'auto' : 'smooth',
block: 'start',
})

Copilot uses AI. Check for mistakes.
Comment thread website/app/sitemap.ts
Comment on lines +7 to +13
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
},
{
url: 'https://boop-agent.vercel.app/docs',
lastModified: new Date(),
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The sitemap entries set lastModified: new Date(), which will change on every sitemap generation and may mislead crawlers. Consider omitting lastModified or deriving it from real content/build metadata so it only changes when the content changes.

Suggested change
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
},
{
url: 'https://boop-agent.vercel.app/docs',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
},
{
url: 'https://boop-agent.vercel.app/docs',

Copilot uses AI. Check for mistakes.
Comment thread website/app/layout.tsx
Comment on lines +18 to +20
metadataBase: new URL('https://boop-agent.vercel.app'),
title: { default: 'Boop Agent', template: '%s | Boop Agent' },
description:
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

metadataBase (and related OG URLs) are hard-coded to https://boop-agent.vercel.app, which will be incorrect for preview deployments, forks, or custom domains. Consider deriving the site URL from an environment variable (with a safe default) and using it consistently.

Copilot uses AI. Check for mistakes.
```
website/
package.json ← Next.js project deps (separate from root)
next.config.ts ← image domains, no extra config needed
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The plan doc lists next.config.ts, but the actual implementation in this PR uses website/next.config.mjs. Updating the file map here will prevent confusion for future readers following the plan.

Suggested change
next.config.ts ← image domains, no extra config needed
next.config.mjs ← image domains, no extra config needed

Copilot uses AI. Check for mistakes.

5. **`'use client'` boundary:** `DocsLayout`, `Navbar`, and `CopyButton` are client components. `DocSection`, all section components, and the page files themselves are server components. Never import a server component from a client component.

6. **Images in markdown:** The README contains `<img>` tags with `assets/` relative paths that won't resolve on the docs page (they point to the GitHub repo). This is expected behavior — images in the rendered markdown will 404. Only the `boop.png` in `public/` is served by Next.js.
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

This note says markdown images using assets/ paths will 404 on the docs page, but the implementation rewrites src="assets/..." to GitHub raw URLs in website/lib/docs.ts. Please update/remove this edge case so the plan doc reflects current behavior.

Suggested change
6. **Images in markdown:** The README contains `<img>` tags with `assets/` relative paths that won't resolve on the docs page (they point to the GitHub repo). This is expected behavior — images in the rendered markdown will 404. Only the `boop.png` in `public/` is served by Next.js.
6. **Images in markdown:** The docs processing rewrites README `<img>` tags that use `assets/` relative paths to GitHub raw URLs, so those images should render correctly on the docs page. Only site-owned static assets placed in `public/` are served directly by Next.js.

Copilot uses AI. Check for mistakes.
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