Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7317b70
Add changelog, showcase, community, and examples pages
aryanmehrotra Apr 8, 2026
0765e19
feat: website overhaul — content, SEO/AEO, AI-discoverability, i18n
aryanmehrotra May 1, 2026
d7ac021
team: add Srijan Rastogi and Vipul Rawat as maintainers
aryanmehrotra May 1, 2026
9407436
team + changelog: card cleanup, sort, pagination, hydration fix
aryanmehrotra May 1, 2026
bc6741c
changelog rail by version, AI-discoverability SEO, click-trap fix
aryanmehrotra May 1, 2026
a320863
seo: lastmod + robots + JSON-LD + freshness bylines (research-backed)
aryanmehrotra May 1, 2026
16425fb
build: take generate-doc-mtimes out of prebuild
aryanmehrotra May 1, 2026
5b5cacd
ci: refresh GitHub-sourced data at deploy time, snapshot-fallback
aryanmehrotra May 1, 2026
5bb3a8c
docker: copy public/ into source-bundle image
aryanmehrotra May 1, 2026
b72170d
feat(team): rank top contributors by composite GitHub-visible score
aryanmehrotra May 1, 2026
cb2fce3
i18n: remove language switcher and Spanish/Chinese hero variants
aryanmehrotra May 1, 2026
d326300
ci: split publishing — :stage on main + cron, :latest on tag
aryanmehrotra May 1, 2026
62cd694
ci: drop publish_stage — stage now builds inline like prod
aryanmehrotra May 1, 2026
cd0186a
ci: re-add publish_stage — :stage as the internal artifact for stage …
aryanmehrotra May 1, 2026
0912f00
content: remove framework-content files now overlaid from gofr-dev/gofr
aryanmehrotra May 1, 2026
aa1a4b5
chore: trim framework-overlaid content from this repo
aryanmehrotra May 1, 2026
26f9d18
seo+a11y: ship critical+high-priority audit fixes
aryanmehrotra May 1, 2026
e9084b4
a11y+aeo: accordion → button, prefers-reduced-motion, llms-full.txt
aryanmehrotra May 1, 2026
e539128
team: remove Srijan + framework fallback for fetch-team locally
aryanmehrotra May 1, 2026
050eebc
seo+a11y+perf: per-route OG/Twitter, root metadata refactor, contrast…
aryanmehrotra May 1, 2026
598fb11
fix(seo): markdoc loader strips parent OG when page has no frontmatter
aryanmehrotra May 1, 2026
5b5d63d
roadmap: hide "Without a milestone" heading, keep cards
aryanmehrotra May 1, 2026
3c86b5a
mobile: fix P0/P1 responsive issues across Hero, Reviews, nav, team
aryanmehrotra May 1, 2026
2a46f32
perf: lazy bugsnag, build-time stars, image opt, prune use-client
aryanmehrotra May 1, 2026
42f2660
header: restore live GitHub stars on every page load
aryanmehrotra May 1, 2026
caca571
fence: defensive default + revert hero xs:px-12; fetch stars at build
aryanmehrotra May 2, 2026
a50788b
images: fix broken-image icons under output: 'export'
aryanmehrotra May 2, 2026
3dd6899
homepage polish + animations + SEO/AEO fix-up batch
aryanmehrotra May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
name: Node.js CI

# Triggers:
# - push to main → publish_stage publishes :stage (internal)
# - push tag v* → release_package publishes :latest + :vX.Y.Z
# - PR to main/development → build job only (no publish)
# - workflow_dispatch → manual button to refresh :stage on demand
#
# Stage deploys (gofr-dev/gofr's website-stage.yml) pull :stage, so
# every merge to website main automatically refreshes what stage shows.
# Prod deploys (website-prod.yml) build inline from this source rather
# than pulling :latest, so prod is always fresh on every framework
# release tag regardless of when the website was last tagged.
on:
push:
branches:
- main
tags:
- 'v*'
pull_request:
branches:
- main
- development
workflow_dispatch:

jobs:
build:
Expand All @@ -24,6 +38,62 @@ jobs:
yarn install
yarn build

# Internal :stage artifact. Published on every push to main + via
# workflow_dispatch. Each run refreshes data so :stage bakes in
# current GoFr release / roadmap / contributor state. Stage deploys
# in gofr-dev/gofr pull this image — pulling is cheaper than
# rebuilding the website from source on every push to gofr's
# development branch.
publish_stage:
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
github.event_name == 'workflow_dispatch'
needs: build
runs-on: ubuntu-latest

permissions:
contents: read
packages: write

steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 18.x

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Refresh data from GitHub (snapshot fallback on failure)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn refresh-data
continue-on-error: true

- name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image (:stage)
uses: docker/build-push-action@v4
with:
push: true
context: .
file: Dockerfile
tags: ghcr.io/gofr-dev/website:stage

# Public :latest artifact. Published on tag v* push as a versioned
# release + the rolling :latest pointer. Prod deploys do NOT pull
# this — they build inline — but it's kept as an external artifact
# and a rollback target.
release_package:
if: ${{ startsWith(github.ref, 'refs/tags/v')}}
needs: build
Expand All @@ -36,6 +106,39 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v4
# Full history is needed by utils/generate-doc-mtimes.mjs which
# reads `git log` per source file to compute the visible
# "Last updated" byline. A shallow clone would give every doc
# the same boot timestamp.
with:
fetch-depth: 0

- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 18.x

- name: Install dependencies
run: yarn install --frozen-lockfile

# Pull fresh data from GitHub at deploy time — releases, roadmap
# milestones, team profiles + contributor list, and per-doc git
# mtimes. The fetch scripts each have a snapshot fallback: on a
# rate-limited or transient failure they keep the previously-
# committed JSON rather than emitting an empty file.
#
# `continue-on-error: true` makes this step non-blocking — even
# in the worst case (every API call failed) the build proceeds
# with the committed snapshots, never an empty website.
#
# Auth uses the workflow's built-in GITHUB_TOKEN (1000 req/hr),
# which is enough for our ~150-request refresh and avoids the
# 60/hr unauthenticated cliff.
- name: Refresh data from GitHub (snapshot fallback on failure)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: yarn refresh-data
continue-on-error: true

- name: Log in to the Container registry
uses: docker/login-action@v1
Expand All @@ -47,7 +150,7 @@ jobs:
- name: Extract Release Tag
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

- name: Build and push Docker image
- name: Build and push Docker image (:latest + :v*)
uses: docker/build-push-action@v4
with:
push: true
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ WORKDIR /app

COPY src src/.
COPY utils utils/.
COPY public public/.
COPY jsconfig.json .
COPY next.config.mjs .
COPY package.json .
Expand Down
70 changes: 69 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,82 @@ import withSearch from './src/markdoc/search.mjs'
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
// `output: 'export'` disables Next's image optimisation API
// (/_next/image). Without `images.unoptimized = true`, every
// <Image> tag emits a /_next/image?url=... src that 404s in
// production and renders as a browser broken-image icon. Setting
// it once here means we don't need to remember `unoptimized` on
// every <Image> usage. PNG/JPG sizes are managed at the source.
images: { unoptimized: true },
pageExtensions: ['js', 'jsx', 'md', 'ts', 'tsx'],
webpack(config) {
config.module.rules.unshift({
test: /\.md$/,
use: [
createLoader(function (source) {
// Inject metadataBase so Open Graph and Twitter image URLs
// resolve to gofr.dev in production builds. The frontmatter
// `nextjs.metadata` block layers title/description on top.
//
// We also auto-derive page-level `openGraph` and `twitter`
// title/description from the frontmatter's top-level title and
// description. Without this, every Markdoc page inherits the
// root layout's openGraph.title / twitter.title — meaning every
// social share preview shows the home page's card. The author of
// a doc can still override by setting `nextjs.metadata.openGraph`
// or `nextjs.metadata.twitter` explicitly in frontmatter.
//
// Derive the route from the resource path (e.g.
// `<repo>/src/app/docs/guides/deploying-to-kubernetes/page.md` →
// `/docs/guides/deploying-to-kubernetes`). We inject this as
// `alternates.canonical` so every Markdoc page emits a unique
// <link rel="canonical">. Next's metadata API does NOT
// auto-derive canonical from metadataBase + pathname; it must
// be set explicitly. The page can override by providing
// `nextjs.metadata.alternates.canonical` in frontmatter.
const resourcePath = (this.resourcePath || '').replace(/\\/g, '/')
const appIdx = resourcePath.indexOf('/src/app/')
let __canonical = '/'
if (appIdx !== -1) {
const rel = resourcePath.slice(appIdx + '/src/app/'.length)
// Strip trailing /page.md
const stripped = rel.replace(/\/page\.md$/, '')
__canonical = '/' + stripped
if (__canonical === '/') __canonical = '/'
}
return (
source + '\nexport const metadata = frontmatter.nextjs?.metadata;'
source +
`
const __pageMeta = frontmatter.nextjs?.metadata || {};
const __pageTitle = typeof __pageMeta.title === 'string' ? __pageMeta.title : undefined;
const __pageDesc = typeof __pageMeta.description === 'string' ? __pageMeta.description : undefined;
const __og = {
...(__pageTitle ? { title: __pageTitle } : {}),
...(__pageDesc ? { description: __pageDesc } : {}),
...(__pageMeta.openGraph || {}),
};
const __tw = {
...(__pageTitle ? { title: __pageTitle } : {}),
...(__pageDesc ? { description: __pageDesc } : {}),
...(__pageMeta.twitter || {}),
};
const __derivedCanonical = ${JSON.stringify(__canonical)};
const __alternates = {
canonical: __derivedCanonical,
...(__pageMeta.alternates || {}),
};
// Only emit openGraph / twitter when the page actually carries
// per-page values. If we always emitted an object, Next's metadata
// merge would replace the parent layout's openGraph wholesale and
// strip its title/description for pages with no frontmatter.
export const metadata = {
metadataBase: new URL("https://gofr.dev"),
...__pageMeta,
alternates: __alternates,
...(Object.keys(__og).length ? { openGraph: { type: 'article', ...__og } } : {}),
...(Object.keys(__tw).length ? { twitter: { card: 'summary_large_image', ...__tw } } : {}),
};
`
)
}),
],
Expand Down
Loading
Loading