Skip to content

Website overhaul (incl. staging-review fixes)#212

Merged
aryanmehrotra merged 7 commits into
mainfrom
feature/website-overhaul
May 5, 2026
Merged

Website overhaul (incl. staging-review fixes)#212
aryanmehrotra merged 7 commits into
mainfrom
feature/website-overhaul

Conversation

@aryanmehrotra

Copy link
Copy Markdown
Member

Summary

Long-running feature branch for the website overhaul. The latest commit on the branch — website: staging-review fixes (see file list below) — addresses the round-up of issues raised in the staging.gofr.dev walkthrough. The earlier commits on this branch are prior overhaul work already iterated on.

What the latest commit changes

Theming / legibility

  • Answer.jsx / Callout.jsx: bold text inside callouts now stays high-contrast on dark backgrounds (was nearly invisible on /comparison).
  • Answer.jsx: rename "Answer" label → Summary.
  • EcosystemRecognition.jsx: collapse rose/sky/amber to a single sky accent + slate-neutral meta.
  • examples/page.jsx: replace 16-color rainbow chips with a sky / emerald / slate 3-bucket palette.

Hero CTA

  • Replace the personal-Calendly tertiary CTA with: Building with an AI coding agent? Hand it our LLM guide → pointing at /llms-full.txt.

Changelog

  • Right rail groups by major version (collapsible <details>); older majors collapsed by default; older entries dimmed.
  • Strip emojis via \p{Extended_Pictographic} (the prior regex missed multi-codepoint sequences and let 💡 Improvements through as � improvements).
  • Map "Improvements" headers to the enhancements chip palette.

Search palette

  • Add a 140-char content snippet under each result, with the matched substring highlighted.

External links

  • New AutoLink component wired into Markdoc's link-node renderer. External URLs (host !== gofr.dev / staging.gofr.dev) get target="_blank" rel="noopener noreferrer"; internal links stay on next/link.

Footer / Roadmap / Team

  • Footer "Production Guides" → /docs/guides/dockerizing-gofr-services.
  • Roadmap "Suggest a feature" → ?template=feature_request.md.
  • Top contributors: 3 cards in a centered grid-cols-1 sm:grid-cols-3 max-w-3xl row (was lg:grid-cols-5 max-w-4xl which left-aligned with empty cells).

Navigation

  • Single "GoFr CLI" entry under References (no flat sub-siblings); per-command pages reachable via direct URL + the overview's in-page command index.

HowTo / SEO

  • HowTo.jsx gains a schema-only mode — passing only steps emits the JSON-LD HowTo silently, so guides can drop a self-closing {% howto steps=[...] /%} for AEO without changing visible chrome.
  • generate-llms-full.mjs: add the production guides section to llms-full.txt.

Build

  • Replace the half-finished Dockerfile with a real two-stage build (node:23-alpine builder → nginx:1.27-alpine runtime serving the static export).
  • New utils/sync-docs.sh — idempotent rsync that pulls gofr-dev/gofr/docs/... into the corresponding src/app/... paths before next build.
  • Add .dockerignore.

Test plan

  • docker build -t gofr-website:local . (after ./utils/sync-docs.sh)
  • docker run --rm -p 3001:3000 gofr-website:local
  • All sentinel routes return 200 and render real content (no 404 fallback): /, /changelog, /examples, /team, /roadmap, /comparison, /why-gofr, /faq, /learn, /migrate, /migrate/from-gin, /docs/quick-start/introduction, /docs/guides/{dockerizing-gofr-services, deploying-to-kubernetes, helm-chart-starter, cloud-deployment}, /docs/references/gofrcli, /docs/references/gofrcli/{init, migrate, wrap-grpc, store}.
  • Spot-checks on rendered HTML confirm: Summary label (not Answer); AI-agent CTA on home; sky-tinted recognition cards; Variant B in dockerizing; "Scraping metrics" section in K8s guide; zop/service callout in helm-chart-starter; feature_request.md template in roadmap; centered 3-card team grid.
  • Pair with the docs PR in gofr-dev/gofr#fix/doc-broken-links.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 5, 2026 10:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR advances the website overhaul and applies the latest staging-review fixes across UI theming, docs/SEO affordances, navigation, search, and build/deploy tooling. It also introduces an external-link-aware Markdoc renderer and improves changelog/search UX for browsing and discovery.

Changes:

  • Add docs-sync + Docker multi-stage static build flow (with nginx runtime) and extend the llms-full.txt generator to include production guides.
  • Improve search UX by adding per-result snippets and update changelog navigation to group releases under collapsible version-line sections.
  • Update multiple UI components (Hero CTA, callouts/summary contrast, footer/roadmap/team/examples styling) and route link behavior via AutoLink.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
utils/sync-docs.sh New rsync-based docs sync script to pull Markdoc pages from the framework repo into this site prior to builds.
utils/generate-llms-full.mjs Include docs/guides in the concatenated llms-full.txt output.
src/markdoc/search.mjs Add snippet generation to search results for improved result previews.
src/markdoc/nodes.js Override Markdoc link rendering to route through the new AutoLink component.
src/lib/navigation.js Update the GoFr CLI navigation description copy.
src/components/Search.jsx Render the new snippet field beneath search results and highlight query text.
src/components/HowTo.jsx Add “schema-only” mode logic intended to emit JSON-LD without visible UI.
src/components/Hero.jsx Replace tertiary CTA with an AI-agent-oriented link to /llms-full.txt.
src/components/Footer.jsx Update “Production Guides” footer link destination.
src/components/EcosystemRecognition.jsx Normalize recognition card accents to a single sky/slate palette.
src/components/Callout.jsx Improve dark-mode contrast for bold text within callouts.
src/components/AutoLink.jsx New component to open external links in a new tab and keep internal links on next/link.
src/components/Answer.jsx Rename “Answer” to “Summary” and improve bold contrast.
src/app/team/page.jsx Adjust top contributors layout to a centered 3-card grid.
src/app/roadmap/page.jsx Point “Suggest a feature” CTA to the feature request template URL.
src/app/examples/page.jsx Simplify tag chip colors to a smaller, consistent palette.
src/app/changelog/page.jsx Improve header emoji stripping, map “Improvements” to enhancements, and group the right-rail by version-line.
Dockerfile Replace prior Dockerfile with a two-stage build (node builder → nginx runtime) for static export.
.dockerignore Add dockerignore entries to reduce build context size.

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

Comment thread src/components/AutoLink.jsx Outdated
Comment on lines +8 to +18
function isExternal(href) {
if (!href) return false
if (href.startsWith('/') || href.startsWith('#')) return false
if (href.startsWith('mailto:') || href.startsWith('tel:')) return false
try {
const url = new URL(href, 'https://gofr.dev')
const host = url.host.replace(/^www\./, '')
return host !== 'gofr.dev' && host !== 'staging.gofr.dev'
} catch {
return false
}
Comment on lines +21 to +33
export function AutoLink({ href, children, ...rest }) {
if (isExternal(href)) {
return (
<a href={href} target="_blank" rel="noopener noreferrer" {...rest}>
{children}
</a>
)
}
return (
<Link href={href || '#'} {...rest}>
{children}
</Link>
)
Comment thread src/components/Search.jsx
Comment on lines +179 to +183
{result.snippet && (
<p className="mt-1 line-clamp-2 text-xs text-slate-400 dark:text-slate-500">
<HighlightQuery text={result.snippet} query={query} />
</p>
)}
Comment thread src/markdoc/search.mjs
Comment on lines +184 to +213
function buildSnippet(content, query, maxLen) {
if (maxLen === undefined) maxLen = 140
if (!content || !query) return undefined
let lower = content.toLowerCase()
let q = query.toLowerCase()
let idx = lower.indexOf(q)
let matchLen = q.length
if (idx === -1) {
// Try matching any single word from a multi-word query
let words = q.split(/\\s+/).filter(function (w) { return w.length > 1 })
for (let i = 0; i < words.length; i++) {
let wIdx = lower.indexOf(words[i])
if (wIdx !== -1) {
idx = wIdx
matchLen = words[i].length
break
}
}
}
if (idx === -1) return undefined
let half = Math.floor((maxLen - matchLen) / 2)
let start = Math.max(0, idx - half)
let end = Math.min(content.length, idx + matchLen + half)
let head = start > 0 ? '\u2026 ' : ''
let tail = end < content.length ? ' \u2026' : ''
let s = content.slice(start, end)
if (start > 0) s = s.replace(/^\\S*\\s/, '')
if (end < content.length) s = s.replace(/\\s\\S*$/, '')
return head + s + tail
}
Comment thread src/components/HowTo.jsx Outdated
Comment on lines +6 to +44
@@ -19,6 +30,19 @@ export function HowTo({ name, description, steps = [], children }) {
}
: null

const hasVisibleContent =
Boolean(name) || Boolean(description) || Boolean(children)

if (!hasVisibleContent) {
// Schema-only: emit JSON-LD with no surrounding chrome.
return jsonLd ? (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
) : null
}
Comment thread src/app/changelog/page.jsx Outdated
Comment on lines +431 to +434
<details
key={major}
open={major === openMajor}
className="group -ml-px"
Comment thread src/app/changelog/page.jsx Outdated
Comment on lines +390 to +411
// Compute a major-line key like "v1.56.x" from a tag like "v1.56.4".
// Tags that don't parse fall into "other" so the rail still has a
// bucket to put them in.
function majorKey(tag) {
const m = tag && /^(v\d+\.\d+)/.exec(tag)
return m ? `${m[1]}.x` : 'other'
}

// Group releases by major-line. Order is preserved from the source
// array (newest first), so the first group is the most recent major.
function groupByMajor(items) {
const groups = new Map()
for (const r of items) {
const key = majorKey(r.tag)
if (!groups.has(key)) groups.set(key, [])
groups.get(key).push(r)
}
return [...groups.entries()]
}

// Right rail — releases grouped by major-version line inside native
// <details>/<summary> blocks. The major containing the active tag (or
Comment thread utils/sync-docs.sh Outdated
dst="$WEBSITE_ROOT/src/app/$route/"
if [[ -d "$src" ]]; then
mkdir -p "$dst"
rsync -a --exclude='layout.jsx' "$src" "$dst"
Comment thread Dockerfile Outdated
Comment on lines +41 to +42
# `yarn refresh-data` on the host before docker build.
RUN yarn next build
aryanmehrotra and others added 2 commits May 5, 2026 16:27
…e, agent-first CTA, search snippets, schema-only HowTo, and proper Dockerfile

Round-up of fixes from the staging.gofr.dev walkthrough:

Theming / legibility
- Answer / Callout: add prose-strong overrides so bold text inside callouts
  stays high-contrast on dark backgrounds (was nearly invisible on /comparison)
- Answer: rename the "Answer" label to "Summary" — pages aren't asking
  questions, so the Q&A framing was misleading
- EcosystemRecognition: collapse rose / sky / amber tints to a single sky
  accent + slate-neutral meta pills; differentiation comes from the actual
  TW / CNCF / JB labels, not from hue
- examples chips: replace the 16-color rainbow with a sky / emerald / slate
  3-bucket palette aligned with the logo

Hero CTA
- Replace the personal-Calendly "block our calendar" tertiary CTA with an
  AI-agent handoff: "Building with an AI coding agent? Hand it our LLM
  guide →" pointing at /llms-full.txt. The site already ships
  llms.txt / llms-full.txt; the new framing matches how agents are
  actually onboarded onto a framework

Changelog
- Group the right-side rail by major version with collapsible <details>
  groups; the active major is open by default, older majors collapse
- Dim entries outside the active major (slate-600 vs slate-500) so the
  current release stands out
- Strip emoji prefixes via \p{Extended_Pictographic} (the previous regex
  missed multi-codepoint sequences and let "💡 Improvements" through as
  "� improvements")
- Map "Improvements" headers to the enhancements chip palette so the chip
  is styled instead of falling through to the slate default

Search palette
- Add a content snippet under the title in search results — 140-char
  window centered on the matched term, with the matched substring
  highlighted via the existing HighlightQuery component

External links
- New AutoLink component wired into Markdoc's link-node renderer:
  external URLs (host !== gofr.dev / staging.gofr.dev) get target="_blank"
  rel="noopener noreferrer"; internal links continue to use next/link for
  prefetching. mailto: / tel: are treated as internal (no new tab).

Footer / Roadmap / Team
- Footer "Production Guides" link points to /docs/guides/dockerizing-gofr-services
  (was deploying-to-kubernetes — wrong as the entry point)
- Roadmap "Suggest a feature" CTA points at the feature_request.md template
  directly, not the GitHub /issues/new/choose chooser
- Top contributors: 3 cards instead of 5; grid changed from
  lg:grid-cols-5 max-w-4xl to grid-cols-1 sm:grid-cols-3 max-w-3xl so the
  3-card row centers cleanly

Navigation
- Revert the GoFr CLI navigation bloat: References shows a single
  "GoFr CLI" entry (matching Context / Configs / Testing weight) instead
  of four flat siblings under it. Per-command pages stay reachable via
  direct URL and the overview's in-page command index

HowTo / SEO
- HowTo component gains a schema-only mode: passing only `steps` (without
  `name`/`description`/`children`) emits the JSON-LD HowTo block silently,
  so guides can drop a self-closing {% howto steps=[...] /%} for AEO
  without changing the visible page
- generate-llms-full.mjs: add the production guides section so the LLM-
  ready index covers the dockerizing / k8s / helm / cloud-deployment
  guides agents are most likely to need

Build
- Replace the half-finished Dockerfile with a real two-stage build:
  builder runs yarn install + yarn next build (output: 'export' produces
  ./out), runtime is nginx:1.27-alpine serving /usr/share/nginx/html
  on port 3000 via the existing nginix.conf
- New utils/sync-docs.sh — idempotent rsync that pulls
  gofr-dev/gofr/docs/{quick-start,advanced-guide,datasources,guides,
  references,faq,learn,why-gofr,migrate,comparison}/ into the
  corresponding src/app/ paths before next build (skips layout.jsx so
  React layout wrappers stay intact)
- Add .dockerignore for a tighter build context

Verified end-to-end via the new Dockerfile against localhost:3001 — all
sentinel routes return 200 and render real content (no 404 fallback).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the hardcoded { gofr.dev, staging.gofr.dev } pair with a single
suffix match against gofr.dev so any current or future subdomain (the
existing staging.gofr.dev, plus future console.gofr.dev / playground
subdomains) is treated as internal — clicking from one gofr.dev
property to another stays in the same tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aryanmehrotra
aryanmehrotra force-pushed the feature/website-overhaul branch from 3cbfa5d to 407e74d Compare May 5, 2026 11:10
aryanmehrotra and others added 5 commits May 5, 2026 16:46
…ight

Two combined bugs broke ↑/↓/Enter in the ⌘K command palette:

1. Items were filtered + URL-deduped at render time inside SearchResults,
   but Algolia tracks active-item state against the *original* collection
   it was given by getItems(). Pressing ↑/↓ updated activeItemId pointing
   at items that had been filtered out before render, and Enter triggered
   onSelect for an invisible item — visible symptom was "buttons do
   nothing".
2. The <li> for each result had no active-state styling, so even when
   activeItemId was correct, the user saw no visual cue that an item
   was focused.

Fix:
- Move filter + URL-dedupe into the source's getItems(), so what the
  autocomplete state holds matches what's rendered. SearchResults now
  just maps and renders.
- Add aria-selected:bg-slate-100 dark:aria-selected:bg-slate-700/30 to
  each <li>. Algolia's getItemProps emits aria-selected="true" on the
  focused item; Tailwind's aria-selected variant picks it up.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… snippet

Searching short queries that match a section heading (e.g. "dat" →
"Datasources") was returning seven visually-identical rows because:

1. Each result rendered its `title` as the headline, but for section-
   level matches across the seven datasource pages the section title
   ("Datasources") is the same on every page. The actual page name
   ("MongoDB", "Redis", etc.) lived in `pageTitle` but wasn't shown
   prominently.
2. The hierarchy line (driven by navigation lookup) often collapsed to
   the same word as the title, adding zero information.
3. Snippets were suppressed for title-matched queries — exactly the
   case where two results share a title and the snippet is the only
   field that disambiguates them.

Fix:

- SearchResult now derives a real page name (`result.pageTitle ||
  result.title`) and renders that as the headline. For section-level
  matches, the section name is appended as `· Section`.
- Adds a URL-derived path breadcrumb (`/docs/datasources/mongodb` →
  "Datasources › Mongodb") as the secondary line. Always present, so
  two results from different pages always look different.
- search.mjs: drop the `titleOnly → snippet=undefined` gate. Always
  build a snippet from page content; falls back to undefined only when
  the query genuinely never appears in the body.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pages without a frontmatter `title:` were getting `pageTitle = "Untitled"`
written into the search index, which my recent search-result rewrite
then promoted to the headline — so `/faq`, `/migrate/from-aspnet-core`,
`/migrate/from-chi`, etc. all rendered as "Untitled · Datasources" in
the ⌘K palette. Useless.

Resolve the page title at index-build time with three fallbacks:
  1. Frontmatter `title:` (preferred)
  2. First body heading (h1/h2 from the AST walk)
  3. URL-derived slug, prettified ("from-aspnet-core" → "From Aspnet Core")

The literal "Untitled" string is now gone from search.mjs entirely. The
runtime `|| 'Untitled'` fallback is replaced with `|| url` as a purely
defensive guard — sections[0][0] is always populated by the loader.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Snippets in the ⌘K palette were leaking raw markdown source — link
syntax `[Text](url)`, heading markers `##`, bold markers `**` — because
the index builder was appending the raw markdown body alongside the
AST-extracted clean text:

  fullRawText = allRawText.join(' ')…
  fullRawText += ' ' + md.replace(/^---[\s\S]*?---/, '').trim()  ← here

A reader searching "configs" on /learn was getting a snippet like
"…and [Configuration Reference](/docs/references/configs). ## Track C
— Building for production **Estimated time: 2–3…" — implementation
details, not content.

Drop the raw-markdown append. The AST walk (text + code + code_block
nodes, recursively) gives clean visible text only: link nodes
contribute the part before the parens, heading/strong/emphasis nodes
contribute their inner text. Snippets are now plain readable English.

Trade-off: searches matching only a URL slug (e.g. "configs" inside
`(/docs/references/configs)`) will no longer surface pages that just
link to that URL. The actual /docs/references/configs page still
ranks first via its own title/content, which is the right behaviour.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot review on PR #212 surfaced 9 inline comments. Eight are real
and fixed here; the ninth (sync-docs.sh idempotency) is moot because
that script is being removed in this commit — the actual deploy-time
docs sync happens in the framework's docs/Dockerfile (which does
`COPY docs/navigation.js /app/src/lib` plus the per-route copies),
not from a script in this repo.

AutoLink (src/components/AutoLink.jsx)
- Block dangerous URL schemes. Anything outside { http:, https:,
  mailto:, tel: } now renders as an inert <span> instead of a
  clickable anchor. Closes the javascript: / data: / vbscript:
  reflection vector for any rogue link in markdoc content.
- Hash-only links (#section) and mailto:/tel: now render as plain
  <a>. next/link is for in-app routing and treats hash-only hrefs
  inconsistently; plain <a> matches native browser scroll behaviour.

HowTo (src/components/HowTo.jsx)
- Schema-only mode is now keyed on `children` (body content), not on
  `name`/`description`. Self-closing `{% howto name="..."
  description="..." steps=[...] /%}` correctly emits JSON-LD with
  no visible chrome — previously the visible <section> rendered
  whenever `name` was present, contradicting the documented intent.
  Verified: db-migrations-in-cicd / horizontal-pod-autoscaler /
  production-tracing now render JSON-LD only.

Search (src/components/Search.jsx, src/markdoc/search.mjs)
- HighlightQuery splits the query into words and passes each as a
  searchWord. Multi-word queries that match a single word in the
  snippet (via buildSnippet's per-word fallback) now actually
  highlight the matched word, instead of trying to highlight the
  full original query and matching nothing.
- buildSnippet clamps `half` to >= 0 so a matched token longer than
  maxLen no longer produces a window that starts past the match
  (start advances and end retreats; the snippet would silently lose
  the match itself).

Changelog rail (src/app/changelog/page.jsx)
- <details open={...}> was effectively controlled — every render
  forced the prop value, overwriting user toggles. Refactor each
  group into ReleaseRailGroup with local useState; useEffect
  reopens the group when it becomes the active version line so
  deep-links still expand the right group, but in-between user
  collapses stick.
- Rename majorKey → versionLineKey (it's <major>.<minor>, not just
  <major>), groupByMajor → groupByVersionLine, activeMajor →
  activeLine. Code now matches what the UI actually shows.

Build (Dockerfile, utils/sync-docs.sh)
- Run the offline prebuild generators (changelog-rss, llms-full)
  inside the Dockerfile before `next build`. Skipping `yarn build`
  also skipped these two even though they don't hit GitHub; the
  container was shipping stale RSS / llms-full.txt. GitHub-API
  fetches stay in CI's `yarn refresh-data` step (rate-limited
  outside CI).
- Drop utils/sync-docs.sh: deploy-time sync is the framework
  docs/Dockerfile's job (FROM ghcr.io/gofr-dev/website + per-route
  COPY of docs/* and docs/navigation.js). The header comment in
  Dockerfile now spells out this overlay pattern.

Navigation (src/lib/navigation.js)
- Restore the missing Production Guides section (18 entries:
  dockerizing, k8s, helm, cloud, ci/cd, db-migrations, tracing,
  prometheus, logging, graceful shutdown, hpa, connection pooling,
  load testing, 12-factor, auth-in-k8s, service mesh,
  multi-environment, distributed tracing). The website-side nav
  had drifted behind the framework's docs/navigation.js; the
  framework's docs/Dockerfile overlays the framework version in
  prod, so this update aligns local-preview rendering with what
  prod actually serves.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aryanmehrotra
aryanmehrotra merged commit 41e9e83 into main May 5, 2026
4 checks passed
@aryanmehrotra
aryanmehrotra deleted the feature/website-overhaul branch May 5, 2026 12:56
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