This repository is the official website for Memos at usememos.com. It is a Next.js + TypeScript + Tailwind app that serves a static marketing/documentation site (Fumadocs + MDX) and an authenticated product surface (a Clerk-gated dashboard and /api handlers).
Treat it as a Next.js 16 marketing/docs site plus an authenticated product app. The marketing/docs surface stays static; the authenticated surface is dynamic. Before adding persistence, a new Cloudflare binding, or a new external dependency, follow the conventions in docs/architecture.md rather than introducing them ad-hoc.
- Framework: Next.js 16 App Router
- Runtime target: Cloudflare Workers through OpenNext
- Content: Fumadocs, MDX, and file-based content under
content/ - Styling: Tailwind CSS 4.x with the local design system
- Icons: Lucide React using the
XxxIconnaming convention - Validation: Zod schemas in
source.config.tsand server route schemas - Auth: Clerk (
@clerk/nextjs), optional and env-gated viaisClerkConfigured() - Node.js: 24.0.0 or newer (pinned in
.nvmrc)
Use pnpm for all package scripts.
| Task | Command | Notes |
|---|---|---|
| Install dependencies | pnpm install |
Runs Fumadocs source generation automatically |
| Develop locally | pnpm dev |
Starts Next.js with Turbopack on port 3000 |
| Run tests | pnpm test |
Vitest (*.test.ts/*.test.tsx); pnpm test:watch to watch |
| Build | pnpm build |
Full production build for the static site |
| Start production server | pnpm start |
Runs the local Next.js production server |
| Lint | pnpm lint |
Runs static revalidation audit, metadata audit, and Biome |
| Format | pnpm format |
Runs Biome format with --write |
| Check and write fixes | pnpm check |
Runs biome check --write |
| Refresh generated docs | pnpm docs:refresh |
Downloads OpenAPI specs, regenerates API MDX, formats it, and rebuilds Fumadocs source |
| Generate Fumadocs source | pnpm docs:generate:source |
Rebuilds ignored .source/ files without downloading API specs |
| Generate Cloudflare types | pnpm typegen |
Writes cloudflare-env.d.ts |
| Build Cloudflare Worker | pnpm run build:worker |
Generates Fumadocs source and builds the OpenNext artifact |
| Cloudflare preview | pnpm run preview |
Builds with OpenNext and serves the Workers runtime on port 8788 |
| Cloudflare deploy | pnpm run deploy |
Deploys an existing OpenNext artifact, preserving existing vars |
| Cloudflare version upload | pnpm run upload |
Uploads an existing OpenNext artifact without promoting it |
| Cloudflare dry run | pnpm run deploy:dry-run |
Builds and packages through Wrangler without deploying |
| Smoke test | SMOKE_BASE_URL=http://localhost:8788 pnpm run smoke |
Run against pnpm run preview |
| Metadata audit | pnpm run metadata:audit |
Checks metadata title rules |
| Open Graph audit | pnpm run og:audit |
Audits OG output |
For day-to-day work, use pnpm dev. Before deployment-sensitive changes, prefer pnpm run preview because it runs the built app in the Cloudflare Workers runtime instead of the local Node.js server.
Standard verification is pnpm test, pnpm lint, and pnpm build, with pnpm run preview plus smoke tests when the change could affect production routing or runtime behavior. CI runs test, lint, and the Cloudflare dry-run build.
See docs/architecture.md for the full architecture and the conventions for expanding the authenticated app (route groups, server-domain layout, the data-access store seam, runtime/caching rules, the auth seam, and a step-by-step for adding an authed feature).
src/app/(public)/contains unauthenticated public routes.src/app/(public)/docs/serves Fumadocs documentation.src/app/(public)/blog/serves blog posts fromcontent/blog/.src/app/(public)/changelog/serves release notes fromcontent/changelog/.src/app/(public)/features/contains the feature index and SEO pages at/features/[slug].src/app/(public)/brand/,pricing/,privacy/,sponsors/, anduse-cases/contain static marketing pages.src/app/(tools)/scratchpad/contains the standalone client-side scratchpad tool.src/app/(app)/is the authenticated product surface (Clerk-gated, noindex, dynamic); the dashboard lives here.src/app/(auth)/holds sign-in/sign-up boundaries. Add authed features followingdocs/architecture.md.
- Documentation MDX lives in
content/docs/. - Blog MDX lives in
content/blog/. - Changelog MDX lives in
content/changelog/. source.config.tsdefines content schemas and MDX processing.src/shared/content/source.tsconfigures Fumadocs loaders for docs, blog, and changelog content..source/is generated byfumadocs-mdx; never edit it directly.
- Feature page data lives under
src/features/marketing/data/features/. - Use-case page data lives under
src/features/marketing/data/use-cases/. - Feature SEO pages are statically generated from feature slugs with
generateStaticParams. - To add a feature page, update the relevant slug, data, and accessor modules together.
- Marketing components:
src/features/marketing/components/ - Docs components and helpers:
src/features/docs/components/andsrc/features/docs/lib/ - Editorial components and helpers:
src/features/editorial/components/andsrc/features/editorial/lib/ - Scratchpad components, hooks, and helpers:
src/features/scratchpad/ - Shared UI primitives:
src/shared/ui/ - Shared layout and navigation config:
src/shared/config/layout.tsx - Shared utilities, including
cn():src/shared/lib/utils.ts
- Prefer existing feature-folder patterns over creating new top-level structures.
- Use the
@/*alias for imports fromsrc/*. - Use
@/.sourceonly for generated Fumadocs output from.source/server.ts. - Import Lucide icons as
XxxIcon, for exampleimport { ShieldIcon } from "lucide-react". - Pass icons as React elements to components, not as string names.
- Keep MDX components registered through
getMDXComponentsinsrc/mdx-components.tsx. - Maintain dark mode support with
dark:classes when changing UI. - Use
py-24for standard marketing section padding androunded-2xlfor card-like surfaces unless the surrounding component uses a different local pattern. - Use the teal/cyan palette, especially
teal-600andcyan-600, for primary marketing actions. - Remove emojis from marketing content; use Lucide icons instead.
- Keep pages mobile-first and responsive.
- Preserve comprehensive Open Graph and Twitter card metadata.
- Keep feature pages search-oriented with clear titles, descriptions, benefits, use cases, and technical details.
- Update
src/app/sitemap.tswhen adding routes that should be discoverable. - Run metadata or OG audits when changing metadata structure.
OpenNext is configured with static-assets incremental caching for this mostly static site. Treat content updates as rebuild and redeploy events. If future ISR or runtime revalidation is required, design a persistent cache backend such as R2 or KV before enabling it.
Cloudflare Workers Builds uses the repository root with these commands:
- Build command:
pnpm run build:worker - Deploy command:
pnpm run deploy - Version command:
pnpm run upload
The build command creates .open-next/ once. The deploy command promotes that artifact to production, while the version command uploads it without promotion for non-production builds.
Use pnpm run preview or pnpm run deploy:dry-run to validate Cloudflare production behavior. pnpm start only validates the local Next.js production server.
postinstallonly regenerates the ignored Fumadocs.source/files. Runpnpm docs:refreshexplicitly when refreshing the committed OpenAPI specs and generated API MDX. The refresh falls back to committedopenapi/*.yamlfiles if the network fetch fails.- pnpm may warn about ignored build scripts for
esbuildandsharp; this is cosmetic for normal development. - The production build generates hundreds of static pages, so
pnpm buildcan take a while. - Do not edit generated files in
.source/. - Auth (Clerk) and
/apihandlers already exist; the required env vars areNEXT_PUBLIC_CLERK_PUBLISHABLE_KEYandCLERK_SECRET_KEY(auth is inert when unset). Do not add a database, a new Cloudflare binding, or a new external dependency ad-hoc — follow the sanctioned path indocs/architecture.md("Future data layer").