(The site is built with Docusaurus)
Stack: Docusaurus 3.10, React 19, Tailwind CSS 4, strict MDX 3.
-
Node. Use the version in
.nvmrc(currently 24;package.jsonrequires 22 or newer).nvm use
-
Clone and install
git clone https://github.com/datazip-inc/olake-docs.git cd olake-docs npm install -
Clear the cache after every
git pull, afternpm install, and after editing any plugin file insrc/plugins(the compile cache does not notice plugin changes). A "Module not found" orReactContextErroron every page usually means a stale cache.npm run clear
-
Start the development server
npm start
Your default browser opens the local site. Some effects are production-only (for example the generated image sizes), so check final results in a build.
-
Build (this is what CI runs; broken links and anchors only produce warnings in the build output)
CI=true npm run build
npm run buildrunsdocusaurus buildand then the image sitemap script. It does not touch the repo: the Open Graph images instatic/img/og/are generated by hand withnpm run og-imagesand committed (see 1.2). To preview the output:npm run serve. -
Optional checks (not run in CI)
Command What it checks npm run typechecktsc --noEmitover the TypeScript sourcesnpm run lint:contentMDX and front matter rules for docs, blog and customer-stories files; details in 1.2. Errors fail the command only when you run it yourself. npm run og-images -- --checkFails if a blog cover has no 1200x630 social card in static/img/og/, or the cover changed since the card was rendered. Writes nothing. Run it after adding a post or changing a cover.
- Tokens:
src/css/tokens.cssholds the--olake-*colors, type scale and spacing for light and dark. Use the Tailwindolake-*utilities (defined insrc/css/custom.css) orvar(--olake-*); do not hard-code hex values in components. - UI primitives:
src/components/landing/ui/(Button,Section,SectionHeading,Card,CtaBanner,LakesidePage). Build marketing pages from these. - Page titles (h1): defined once in
src/css/typography.css; do not set h1 size or weight in a component. - Swizzling: wrap the original (
@theme-original/<Component>) instead of copying it, so the swizzle survives Docusaurus upgrades.
CI (test-deploy.yml on pull requests, deploy.yml on master) only builds the site; none of the checks below block a merge, and the legacy MDX syntax (<!-- --> comments, {#id} heading ids, :::note Title) still compiles. They are there so you can catch problems before opening a PR.
Run it locally
npm run lint:content # errors fail (exit 1), warnings are listed
npm run lint:content -- blog/2026-10-01-my-post.mdx # limit to a file or folder
npm run lint:content -- --strict # warnings fail too (optional, not used in CI)
npm run build # broken links and anchors are printed as warningsErrors (fail lint:content when you run it). The preferred form for each:
| Rule | Correct form |
|---|---|
html-comment |
{/* comment */}, never <!-- comment --> (only <!-- truncate --> is allowed) |
heading-id |
## Install {/* #install */}, never ## Install {#install} |
admonition-title |
:::tip[Before you start], never :::tip Before you start |
second-h1 (posts) |
The title comes from front matter; start body headings at ## and do not use # ... or <h1> |
old-blog-url |
[Post](/blog/my-post-slug/), never /blog/2024/05/01/... |
title-length (posts) |
title: at most 70 characters (put longer SEO wording in seo_title:) |
title-missing, title-entity (posts) |
title: "Postgres to Iceberg", with no & or & |
description-missing |
Every post and doc has a description: |
authors-missing, author-unknown |
authors: [anshika], where the id exists in the folder's authors.yml |
tags-missing, tags-count, tag-undefined |
tags: [olake, iceberg]: at least one, at most 8, each defined in the folder's tags.yml |
image-missing, image-path, image-not-found |
image: /img/blog/cover/my-cover.webp starts with / and the file exists under static/ |
slug-space, slug-duplicate |
Slugs are unique per folder and contain no spaces |
filename-space |
No spaces in file names |
frontmatter-yaml |
Front matter parses as YAML (quote values that contain :) |
unclosed-fence |
Every opening code fence has a closing one |
Warnings (listed only; fix them in files you touch). A summary written as :::tip[TL;DR] or ## TL;DR instead of the component, a description outside 120-160 characters, <br> outside a table, bold lines used as headings, code fences without a language, heading level jumps, absolute https://olake.io/... links, a post that does not end with <BlogCTA/>, docs/shared partials that have front matter, and a file name date that differs from the date: field.
TLDR component. Write the post summary once, right after the opening paragraph, as a component with a blank line after the opening tag and before the closing tag:
<TLDR>
- Point one
- Point two
</TLDR>Internal links. Link to /docs/, not /docs/intro/. lint:content does not check this. The build prints a warning for a link to a route that does not exist, so read the build output. A plain <section id="x"> is not collected as an anchor, so a <Link to="#x"> to it is reported as broken even though the id exists; use <a href="#x"> for in-page links on custom pages.
Common fixes
heading-idorhtml-commenton old content: rewrite the syntax by hand as shown above. Do not usenpm run write-heading-ids, it writes the old{#id}form.title-length: shortentitle:and move the full phrase toseo_title:. It feeds the page title,og:titleandtwitter:title, while the visible H1 stays short.tag-undefinedorauthor-unknown: add the entry to that folder'stags.ymlorauthors.yml, or pick an existing one.image-not-found: check the case and extension of the file name understatic/img/.- Broken link or anchor warning in the build output: it names the page and the target; fix the target or the heading id it points to.
Open Graph images. Every post cover needs a 1200x630 card in static/img/og/ (the post page points og:image and twitter:image at it). They are generated by hand and committed, not on every build:
npm run og-images # render only missing or stale cards, update scripts/og-images.manifest.json
npm run og-images -- --force # re-render every card
npm run og-images -- --check # write nothing, exit 1 if a card is missing or staleA card is stale when the SHA-256 of its source cover no longer matches scripts/og-images.manifest.json. File times are not used. After adding a post or changing a cover, run npm run og-images and commit the new card and the manifest.
| Directory | When to use |
|---|---|
/blog |
OLake and Iceberg topics |
-
Navigate to the correct directory.
-
Create an MDX file:
YYYY-MM-DD-blog-slug.mdx. The date reflects the publish date, not the commit date. -
Copy the front-matter (metadata) from an existing post and update it.
-
Manage authors
- Each directory has its own
authors.yml. - Add new authors here before referencing them in a post.
- Each directory has its own
-
Append
<BlogCTA/>as the final line of every blog post.
- Root folder:
static/img/blog/ - Cover images:
static/img/blog/cover/<slug>-cover.png - Inline images:
static/img/blog/YYYY/MM/<slug>-N.png
Example – slug flatten-array, published May 2025:
static/img/blog/cover/flatten-array-cover.webp
static/img/blog/2025/05/flatten-array-1.webp
static/img/blog/2025/05/flatten-array-2.webp
-
Create reusable React (
.tsx) components insrc/components.- Keep them text-agnostic and modular.
-
Before creating, scan existing components for reuse.
-
To expose a component globally in MDX, import it once in
src/theme/MDXComponents/Index.js.- Afterwards, you can use
<MyComponent/>in any file under/blogor/docs. - HTML pages in
src/pagesstill require manual imports.
- Afterwards, you can use
- Create the MDX file under
/docs. - Register the file (and its order) in
sidebars.js. Order-of-appearance in the sidebar is defined here.
- Place reusable snippets in
docs/shared/. - Import them in
src/theme/MDXComponents/Index.js. - Use them anywhere with the usual
<SharedComponent/>syntax. docs/shared/is excluded from the docs build (excludeindocusaurus.config.js), so snippets are not published as pages or listed in the sitemap. They only render where they are imported. Do not link to/docs/shared/...URLs.
| Topic | Details |
|---|---|
| Redirects | Configure in the @docusaurus/plugin-client-redirects list in docusaurus.config.js. Never leave a "moved" stub .mdx behind (see 6.5). |
CNAME |
Do not delete static/CNAME; GitHub Pages needs it for olake.io. |
| Public files | Any asset in static is publicly accessible (e.g. /reddit.json). |
| Next.js | We may switch to the Next.js plug-in later for Lighthouse gains. |
| Embedding media | Images: Markdown ; use raw HTML for size control.Videos: embed via HTML. |
| MDX & HTML | MDX accepts all HTML except tables (use Markdown tables). |
| Docusaurus features | Components like <Tabs>, <TabItem>, :::info, etc. are native—see their docs. |
```py title="docker-compose.yml"
# code here
````
Supported lexers: py, bash, sh, js, jsx, go, yaml, yml, text, …
## Heading ✅
### Heading ✅
## **Heading** ❌
Use Docusaurus swizzle to override core components and UI of how . Swizzled files live in src/theme/.
We have already swizzled: blog pages, doc pages, navbar, and footer.
Add work-in-progress content to the root /drafts folder; it will not be published.
A draft that must live inside docs/, blog/ etc. needs draft: true in its frontmatter. A folder called docs/drafts/ is not hidden automatically: a page there is published and goes into the sitemap unless it has draft: true.
Breaking these has cost search traffic before. Run npm run build and npm run lint:content and look at the output before opening a PR.
- Canonical URLs keep the trailing slash, and Docusaurus writes them. The site is
trailingSlash: true; GitHub Pages 301s the no-slash URL to the slash URL, and the sitemap and hreflang use the slash URL. Never add your own<link rel="canonical">and never strip the slash from a permalink.og:urland JSON-LDurl/@idmust match the canonical. Before the fix Google indexed both versions of 172 pages. - Hardcoded internal links end in a slash (
/slack/,/contact/,/blog/, also absolutehttps://olake.io/...links in MDX, which Docusaurus does not normalise). UseSLACK_URLinstead of retyping the Slack link. A link without the slash costs a 301 hop on every click and crawl. - Changing or removing a URL needs a redirect. If you change a post's
slug, rename a doc or delete a page, add{ from, to }to the client-redirects list indocusaurus.config.jsin the same PR. In 2026-04 a top post changed slug without one and kept 404ing in search with about 500 clicks of history. Do not keep a "(Moved)" stub.mdxpage. Put a slash before any#or?in a redirectto;src/plugins/client-redirects-slashrestores it in the generated stubs because Docusaurus drops it. - Titles. The site title is
OLake, so every<title>ends in| OLake(docs too). Keep the rendered title at 60 characters or fewer, so the frontmatter title at about 52. If the visible H1 needs to be longer, addseo_title: "..."(about 52 characters) to the frontmatter: it replaces only<title>,og:titleandtwitter:title, and the H1 keepstitle. Docs titles under 25 characters get their sidebar section appended automatically. Code:src/theme/seo/helpers.ts,src/theme/DocItem/Metadata,src/theme/BlogPostPage. - Descriptions. Every page needs its own
descriptionof 120 to 155 characters (max 160, min 100), unique across the site. Do not copy one post's description into another, and do not leave it truncated with "...". Files indocs/sharedare snippets and have no frontmatter. - Pages that must not be indexed get
noindex, follow: confirmation pages, archive pages, tag and author pagination, zero-post authors,/search/. The theme does this for the generated pages; add it yourself to any new thank-you or utility page. Keep them crawlable (no robots.txt block), otherwise Google never sees the tag. - Taxonomy pages (blog and customer-stories archive, authors, tags) get their title, description and H1 from the swizzled components in
src/theme(Blog/useBlogInstance.js). Titles carry no post count (the count made SEO audits report "title changed" on every new post). A new blog instance must be added there, or its pages fall back to the "Blog" wording. - Sitemap exclusions live in the sitemap options of
docusaurus.config.js(tags, authors, archive, search,docs/shared, confirmation pages, pagination).docs/sharedis not built as pages at all. - One
og:type, one H1, one JSON-LD per schema. Pages undersrc/pagesgetog:type=websitefromsrc/theme/Layout; posts emitarticle. Emit JSON-LD withsrc/components/JsonLd, not<Head><script dangerouslySetInnerHTML>(Helmet drops it). - Images. Keep every image under about 300 KB (WebP; no GIFs, use short muted looping video).
git add . # stage all changes
git commit -m "Message" # commit
git commit -am "Message" # add + commit tracked files
git pull # fetch & merge
git push # push current branch
git branch # list branches
git status # show working tree status
git checkout -b <new-branch> # create and switch
git switch <branch> # switch existing branchgit push --set-upstream origin <branch-name># 1. Make sure local master is current
git switch master
git pull origin master
# 2. Create a feature branch
git checkout -b blog/your-slug # or docs/your-topic, etc.
# 3. Work on your changes
# (add blog MDX, images, authors.yml, sidebar entry, etc.)
# 4. Stage & commit
git add .
git commit -m "Add <your-slug> blog to OLake docs"
# 5. Push and open a PR
git push --set-upstream origin blog/your-slug-
Finish work on the first branch and push.
-
Ensure
masteris up to date:git switch master git pull origin master
-
Create your next branch:
git checkout -b blog/second-slug
-
Repeat the add → commit → push cycle.
Tips • Git can feel tricky—refer to official docs, blog posts, or ask in Slack. • Always branch from
masterunless directed otherwise.
- Check the official Docusaurus docs.
- For Git issues, consult documentation or ChatGPT.
Happy writing and shipping content!