The landing page and OAuth callback relay for Orange Cloud, deployed at o-c.do on Cloudflare Workers.
- Landing page in five locales (en / zh-Hans / zh-Hant / zh-HK / ja) via
next-intl, withlocalePrefix: "as-needed"— English lives at/, other locales at/zh-Hansetc. Also serves/privacy,/terms, and/contact, which the iOS app links to. - OAuth callback relay at
/oauth/callback(route.ts): Cloudflare's OAuth only acceptshttpsredirect URIs, so this route 302-redirects the authorizationcodeandstatestraight to the iOS app's custom scheme (orangecloud://oauth/callback). It stores nothing and never exchanges the code — the token exchange andstatevalidation happen on-device, secured by PKCE.
- Next.js 16 (App Router) + React 19
next-intlfor routing/messages — translations live insrc/messages/- Tailwind CSS 4
- Deployed to Cloudflare Workers with
@opennextjs/cloudflare; config inwrangler.jsonc
/oauth/callbacksits outside the[locale]segment, and the middleware matcher insrc/middleware.tsexplicitly excludesoauth— the callback must reach the route handler untouched. Don't let locale routing swallow it.- On Cloudflare Workers, next-intl's middleware must be an edge
middleware.ts— the Next 16proxy.tsconvention runs on the Node runtime, which Workers doesn't support. - The screenshot gallery images live in
public/shots/<locale>/(01_dashboard.jpg…); zh-HK reuses the zh-Hant set.
What's wired up:
-
robots.tstiers AI crawlers: search/retrieval bots (OAI-SearchBot, Claude-SearchBot, PerplexityBot) and user-triggered bots (ChatGPT-User, Claude-User, Perplexity-User) are allowed; training crawlers (GPTBot, ClaudeBot, Meta-ExternalAgent, CCBot), opt-out tokens (Google-Extended, Applebot-Extended), and undeclared crawlers (Bytespider) are blocked./oauth/is excluded everywhere. -
public/llms.txt— Markdown overview of the product, key pages, pricing, and the GitHub repo for AI retrieval (spec). -
sitemap.tsemits all pages with full hreflang alternates;generateMetadatacovers canonical, hreflang, Open Graph, Twitter card, anditunesapp association. -
JSON-LD (
SoftwareApplication) in the locale layout — only the Bing/Copilot index-enrichment path is known to consume it, so it stays minimal. -
IndexNow key at
public/ae4368227a78d73327c42c34949e9075.txt. After publishing new content, ping:curl -X POST "https://api.indexnow.org/indexnow" -H "Content-Type: application/json" -d '{ "host": "o-c.do", "key": "ae4368227a78d73327c42c34949e9075", "urlList": ["https://o-c.do/"] }'
One-time manual steps (dashboard accounts required): verify the domain in Google Search Console and Bing Webmaster Tools, submit https://o-c.do/sitemap.xml in both, and optionally list llms.txt at directory.llmstxt.cloud / llmstxt.site.
From the repo root (pnpm workspace):
pnpm install
pnpm dev # turbo dev --filter=web → next dev on http://localhost:3000Or inside apps/web/:
pnpm dev # Next.js dev server
pnpm preview # build with OpenNext and preview on the Workers runtime
pnpm cf-typegen # regenerate cloudflare-env.d.ts after changing wrangler.jsoncpnpm deploy # opennextjs-cloudflare build && deployThe official deployment uses the custom domain o-c.do (configured in wrangler.jsonc routes). For your own fork: change the name and routes in wrangler.jsonc, deploy under your own Cloudflare account, then register https://<your-domain>/oauth/callback as the redirect URI of your own Cloudflare OAuth client (see CONTRIBUTING.md).