How I Built Wikibeem: Turning ClickUp Docs into Professional Documentation Sites
Source: Dev.to
A solo dev journey from frustration to product launch
I love ClickUp. It’s where my team lives — tasks, docs, wikis, everything connected.
But every time I needed to share documentation with a client, I hit the same wall:
https://doc.clickup.com/d/2kxuepwx-192
That ugly, branded, unprofessional URL. Clients would ask: “Why are we using ClickUp? Can you put this on your website?”
I spent hours:
- Exporting to PDF (formatting breaks)
- Copy‑pasting to Notion (links break)
- Rebuilding in GitBook (context lost)
Workarounds like Cloakist only proxied the ClickUp page, so clients could still click around and see internal stuff.
The problem is common
I searched Reddit, ClickUp forums, and feedback boards. Turns out I wasn’t alone:
- “Shared doc.clickup.com with a prospect. They thought we use ClickUp internally and bailed.” — r/clickup
- “Want to share the onboarding section. Can’t without exposing the entire task roadmap.” — feedback.clickup.com
- “Docs export breaks embeds, bullets vanish. Recreating in Notion costs 4 hours of work.” — Agency PM on Reddit
Hundreds of people had the same frustration: agencies, SaaS teams, freelancers — all stuck with the same problem.
Introducing Wikibeem
One night I thought: What if I could solve this for myself and for them?
That’s how Wikibeem was born.
Tech stack
| Component | Technology |
|---|---|
| Frontend / Server | Next.js 16 (App Router), React 19, Tailwind CSS 4 |
| Data layer | PostgreSQL + Prisma ORM |
| Hosting | Vercel (edge functions) |
| Auth | NextAuth v5 (beta) – credentials + OAuth |
| Payments | Paddle (handles global taxes) |
| ClickUp integration | ClickUp API v3 (OAuth 2.0) |
| HTTP client | Axios |
| Sync engine | Custom logic for nested pages, wikis, doc hierarchies |
| Markdown ↔ HTML | Marked, Cheerio, Turndown |
| Search | Fuse.js (client‑side fuzzy search) |
| SEO | Per‑site & per‑document SEO, auto‑generated sitemaps |
| Domains | Vercel SDK (programmatic custom domain setup, automatic SSL) |
| Internationalisation | next‑intl (9 languages: EN, FR, DE, ES, PT, IT, RU, AR, ZH) |
How Wikibeem works (under the hood)
┌─────────────────┐ OAuth ┌─────────────────┐
│ ClickUp API │◄──────────────►│ Wikibeem │
│ (Your Docs) │ │ (Next.js) │
└─────────────────┘ └────────┬────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ PostgreSQL │ │ Vercel │ │ Paddle │
│ (Prisma) │ │ (Domains) │ │ (Payments) │
└───────────┘ └─────────────┘ └─────────────┘
Data model
User
└── Workspace (ClickUp connection)
└── Site (your docs website)
├── Documents (synced from ClickUp)
│ └── Children (nested pages)
├── Domain (custom domain)
├── Theme (colors, logo)
└── SEO Settings
Sync engine details
ClickUp’s API returns docs that can contain:
- Nested pages
- Wikis with their own structure
- Content in various formats (JSON blocks, Markdown, HTML)
The engine must:
- Fetch all docs from a workspace.
- Recursively process pages and their children.
- Convert content to clean HTML.
- Build a hierarchy with parent‑child relationships.
- Generate unique slugs for URLs.
- Handle updates without creating duplicates.
Handling root‑doc duplication
// The fix: Track root doc IDs and filter them out
const rootDocIds = new Set(docs.map(d => d.id))
// When processing pages, skip if it's actually a root doc
if (rootDocIds.has(page.id)) {
continue // This page is a doc, not a child page
}
Optimising slug checks
The first version queried the database for each slug, resulting in 100+ round trips for 100 pages.
// In‑memory slug tracking
const existingSlugs = new Set()
// Instead of: await prisma.document.findUnique(...)
// Now:
if (existingSlugs.has(slug)) {
// generate a new slug
}
existingSlugs.add(newSlug) // Track immediately
Sync time dropped from 30 + seconds to under 10 seconds.
Programmatic domain provisioning (Vercel SDK)
import { Vercel } from '@vercel/sdk'
const vercel = new Vercel({ accessToken: process.env.VERCEL_TOKEN })
// Add domain
await vercel.projects.addProjectDomain({
idOrName: projectId,
requestBody: { name: 'docs.yourcompany.com' }
})
// Get verification records
const config = await vercel.domains.getDomainConfig({
domain: 'docs.yourcompany.com'
})
The user adds a CNAME record, clicks “Verify,” and the docs go live on their custom domain with HTTPS automatically.
Future roadmap (features in progress)
- Real‑time sync via ClickUp webhooks
- Analytics – see which docs people read
- Password‑protected docs – for private client portals
- Additional themes
- API access – for power users
Get started
Wikibeem is live at wikibeem.com.
- Connect your ClickUp workspace.
- Sync your docs.
- Add your custom domain.
Under 5 minutes to a professional documentation site.
Feedback
I’m building this in public and genuinely want your input. What features would make this useful for you? What’s missing?
Reach out on Twitter/X or LinkedIn. Let’s build this together.