diff --git a/netlify-build.sh b/netlify-build.sh
new file mode 100644
index 0000000..3a94a43
--- /dev/null
+++ b/netlify-build.sh
@@ -0,0 +1,141 @@
+#!/usr/bin/env bash
+set -euxo pipefail
+
+# 1. Install tools and dependencies
+
+curl https://mise.run | sh
+mise trust
+mise install
+mise exec -- bun install --frozen-lockfile
+
+# 2. Prepare common files
+
+mkdir _site
+
+# Prepare favicon
+curl -L https://github.com/typst-community/org/raw/main/design/typst-community.icon.png \
+ -o _site/favicon.png
+cp _site/favicon.png public/favicon.png
+
+# Prepare the index page
+REF=$(git rev-parse --short HEAD)
+DATE=$(git log --max-count=1 --pretty='%cd' --date=iso)
+cat << EOF > _site/index.html
+
+
+
+
+
+ typst-docs-web
+
+
+
+
+
+ Build a website from the documentation JSON file generated by typst-docs.
+ This website is for developing typst-docs-web. Its contents might be changed at anytime.
+
+
+ typst-docs-web version:
+ $REF ($DATE)
+
+
+
+
+EOF
+
+# 3. Build
+# 3.1. Build en-US
+
+build_en_US() {
+ local VERSION="$1"
+
+ BASE="en-US-$VERSION"
+
+ # Prepare docs.json
+ curl -L https://github.com/typst-community/dev-builds/releases/download/docs-"$VERSION"/docs.json \
+ -o public/docs.json
+ sd '/DOCS-BASE/' "/$BASE/" public/docs.json
+
+ # Prepare docs assets
+ curl -LO https://github.com/typst-community/dev-builds/releases/download/docs-"$VERSION"/docs-assets.zip
+ unzip docs-assets.zip && rm docs-assets.zip
+ mv assets public/assets
+
+ # Configure metadata
+ cat << EOF > public/metadata.json
+{
+ "\$schema": "../metadata.schema.json",
+ "language": "en-US",
+ "version": "${VERSION#v}",
+ "typstOfficialUrl": "https://typst.app",
+ "typstOfficialDocsUrl": "https://typst.app/docs/",
+ "githubOrganizationUrl": "https://github.com/typst-community",
+ "githubRepositoryUrl": "https://github.com/typst-community/typst-docs-web",
+ "discordServerUrl": "https://discord.gg/2uDybryKPe",
+ "originUrl": "${DEPLOY_URL:-https://example.com}/",
+ "basePath": "/$BASE/",
+ "displayTranslationStatus": false
+}
+EOF
+ # $DEPLOY_URL will be set by netlify. Fallback to example.com for local testing.
+ # https://docs.netlify.com/build/configure-builds/environment-variables/#deploy-urls-and-metadata
+
+ # Build
+ mise exec -- bun run build
+ mv dist _site/"$BASE"
+
+ # Clean
+ rm -r public/{docs.json,assets,metadata.json}
+}
+
+build_en_US v0.14.0
+build_en_US v0.13.1
+
+# 3.2. Build ja-JP
+
+# Prepare JSON files
+mise exec -- bun run fetch-docs-ja-jp
+sd '"/docs/' '"/ja-JP/' public/docs.json
+sd --fixed-strings \
+ '"basePath": "/docs/",' \
+ '"basePath": "/ja-JP/",' \
+ public/metadata.json
+
+# Prepare docs assets
+# At present, typst-jp do not translate comments within example code.
+# And there is no simple way to download assets from GitHub Actions or the gh-pages branch.
+# Therefore, we reuse the assets from the official version.
+curl -LO https://github.com/typst-community/dev-builds/releases/download/docs-v0.13.1/docs-assets.zip
+unzip docs-assets.zip && rm docs-assets.zip
+mv assets public/assets
+
+# Build
+mise exec -- bun run build
+mv dist _site/ja-JP
+
+# Clean
+rm -r public/{docs,metadata,translation-status}.json public/assets
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..5816a23
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,3 @@
+[build]
+command = "bash netlify-build.sh"
+publish = "_site"