website-content-editor
Website Content Editor
Make content and image changes to the Tiger Data website. Reads and updates files in the timescale/timescale-website repo via the GitHub API (no local clone needed), opens a PR, and provides the Vercel preview link so stakeholders can review visually.
Platform notes
- Claude Code: Uses
ghCLI commands in the terminal. Works ifgh auth statusshows access totimescale/timescale-website. - Cowork: Uses the GitHub MCP server tools. Requires the
githubMCP server to be configured. Ifget_me()fails or is unavailable, tell the user to add the GitHub MCP server to their Cowork configuration.
When to use this skill
- User wants to update text, headlines, CTAs, or copy on tigerdata.com
- User wants to update or replace images on tigerdata.com
- User asks to edit a landing page, product page, or any static content page
- User mentions "website content", "website copy", or "update the website"
- User mentions "update images", "replace images", or "swap out an image"
- User wants to open a PR against the website repo with content or image changes
What this skill cannot change
- Blog posts — managed in Ghost CMS, not editable via code
- Learn/education pages — managed in Contentful CMS, not editable via code
- Plasmic-designed pages (events, go) — managed in Plasmic visual builder
If the user asks to change content in any of these areas, explain which CMS manages it and redirect them to the appropriate tool.
Step 0: No Fly List check
If Tiger Den is available, fetch the No Fly List before making any changes:
get_marketing_reference(slug: "no-fly-list")
If Tiger Den is not available, warn the user: "No Fly List check skipped — Tiger Den is not connected. Verify manually that no restricted customers are being added to the website." Then proceed.
If Tiger Den is available, load the returned names as a hard constraint: never add any No Fly List customer to website content — not as named examples, proof points, customer quotes, or case study references. If the user asks you to add or reference a No Fly List customer anywhere on the website, stop and inform them that this customer cannot be publicly referenced.
Instructions
1. Verify GitHub access
Claude Code: Run gh auth status and confirm the authenticated account has access to timescale/timescale-website. If not authenticated, tell the user to run gh auth login.
Cowork: Call get_me() to verify the GitHub MCP server is connected and returns a valid user. If it fails or is unavailable, tell the user to add the GitHub MCP server to their Cowork configuration.
2. Understand the request
Ask the user:
- Which page(s) to change (URL or page name)
- What the current text says vs. what it should say
- Whether the change involves images — if so, ask for the image file (must be a compressed
.png; reject other formats and ask the user to convert/compress first) - Any context on why the change is needed (helps write the PR description)
3. Map URL to file path
The website is a Next.js app. Page content lives under app/ with route groups:
tigerdata.com/{slug}maps toapp/(with-footer)/{slug}/page.tsxorapp/(no-footer)/{slug}/page.tsxorapp/(landing-pages)/{slug}/page.tsx- Content is split across:
page.tsx— JSX rendering and layout_data.tsx— static data (arrays, strings, config objects)_components/— page-specific components
- Shared constants live in
src/data/constants.ts
To find the exact path, list the app directory via the GitHub API:
Claude Code:
gh api repos/timescale/timescale-website/contents/app --jq '.[].name'
Then drill into route groups and subdirectories to locate the target page:
gh api "repos/timescale/timescale-website/contents/app/(with-footer)" --jq '.[].name'
gh api "repos/timescale/timescale-website/contents/app/(with-footer)/{slug}" --jq '.[].name'
Cowork: Use the GitHub MCP get_file_contents tool — when called on a directory it returns a listing:
get_file_contents(owner: "timescale", repo: "timescale-website", path: "app")
get_file_contents(owner: "timescale", repo: "timescale-website", path: "app/(with-footer)")
get_file_contents(owner: "timescale", repo: "timescale-website", path: "app/(with-footer)/{slug}")
4. Read current file(s)
Claude Code: Fetch the file content via the GitHub Contents API and base64-decode it:
gh api repos/timescale/timescale-website/contents/{path} --jq '.content' | base64 --decode
Save the file's current SHA for the update step:
gh api repos/timescale/timescale-website/contents/{path} --jq '.sha'
Cowork: Use get_file_contents — it returns decoded content directly (no base64 handling needed). Note the file SHA from the response for the commit step.
get_file_contents(owner: "timescale", repo: "timescale-website", path: "{path}")
Identify the exact text to change. Show the user the current text and confirm the replacement before proceeding.
4a. Read constants and reference files before editing
Before writing any changes, fetch and review the project's shared constants and config files. The website uses pre-defined constants for strings, URLs, CTA labels, colors, and reusable values — hardcoding values that already exist as constants will break conventions and cause PR rejections.
Key reference files to check:
| Reference | Path |
|---|---|
| CTA text and URLs | src/data/constants.ts |
| Color palette | src/config/tailwind/colors.js |
| Tailwind config | tailwind.config.ts |
| Project conventions | README.md |
Claude Code:
gh api repos/timescale/timescale-website/contents/src/data/constants.ts --jq '.content' | base64 --decode
Cowork:
get_file_contents(owner: "timescale", repo: "timescale-website", path: "src/data/constants.ts")
Also read any _data.tsx file in the same page directory, and check the import statements at the top of the file you're editing for other const/config imports.
Rules for using constants:
- URLs must use consts. The constants file defines URL consts like
BLOG_URL,DOCS_URL,CONSOLE_URL,GITHUB_URL, etc. Always import and use these to construct links — never hardcode a URL string like"https://docs.tigerdata.com"or"/blog". If you need to build a path (e.g., a specific blog post), use the const as the base:`${BLOG_URL}/post-slug` - CTA labels must use consts. Shared button/link text like
CONSOLE_CTA_TEXT,CONTACT_CTA_TEXTalready exists — import and use it instead of writing the raw string - If a color is defined in the Tailwind palette, use the Tailwind class — never hardcode hex values
- If the value you need doesn't exist as a constant but is used in multiple places, add it to the appropriate const file and import it
- When editing
_data.tsxor component files, preserve existing const imports and patterns — match the style of surrounding code
4b. Handle image updates
When the request includes image changes, follow these additional steps.
Format requirement: Only compressed .png files are accepted. If the user provides a JPEG, SVG, WebP, or any other format, ask them to convert and compress it to PNG before proceeding. Do not attempt to convert images.
Image path convention: All images go under public/images/{page-name}/ in the repo. Derive {page-name} from the page's URL slug — for example, an image for the pricing page goes in public/images/pricing/hero.png.
Claude Code: Base64-encode the PNG file and commit it via the Contents API (same as text files, but with binary content):
base64 -i /path/to/local/image.png | tr -d '\n'
Then commit using the same gh api PUT call from step 6.
Cowork: Use create_or_update_file with the base64-encoded PNG content, or include the image in push_files alongside text changes (see step 6).
Update the code reference: After committing the image, update the corresponding TSX/component file to reference the new image path. Use the Next.js Image component import pattern or a standard src attribute pointing to /images/{page-name}/{filename}.png. Locate the existing image reference in the component and replace it with the new path.
5. Create a branch
Claude Code:
Get the latest SHA from main:
gh api repos/timescale/timescale-website/git/ref/heads/main --jq '.object.sha'
Create a new branch from that SHA:
gh api repos/timescale/timescale-website/git/refs \
-f ref="refs/heads/{branch-name}" \
-f sha="{main-sha}"
Cowork: Use the GitHub MCP create_branch tool:
create_branch(owner: "timescale", repo: "timescale-website", branch_name: "{branch-name}", from_ref: "main")
Branch naming conventions:
chore/short-descriptionfor copy edits and minor text changesfeat/short-descriptionfor new content sections or pages
6. Commit changes
Claude Code: For each file to update, base64-encode the full updated file content and commit via the Contents API:
echo -n '{updated-file-content}' | base64 | tr -d '\n'
Then push the update:
gh api repos/timescale/timescale-website/contents/{path} \
-X PUT \
-f message="{commit-message}" \
-f content="{base64-encoded-content}" \
-f sha="{current-file-sha}" \
-f branch="{branch-name}"
Cowork — single file: Use create_or_update_file:
create_or_update_file(owner: "timescale", repo: "timescale-website", path: "{path}", message: "{commit-message}", content: "{updated-file-content}", branch: "{branch-name}", sha: "{current-file-sha}")
Cowork — multiple files: Use push_files to commit all changes in a single commit:
push_files(owner: "timescale", repo: "timescale-website", branch: "{branch-name}", files: [{path: "{path1}", content: "{content1}"}, {path: "{path2}", content: "{content2}"}], message: "{commit-message}")
Write clear commit messages that describe what changed and why.
Image commits: Image files use the same API calls but the content is already binary (base64-encoded PNG). When committing both text and image changes, prefer a single commit — use push_files in Cowork or sequential Contents API calls on the same branch in Claude Code.
7. Verify the build passes (Claude Code only)
Before opening a PR, clone the branch and run the build to catch errors. Do not skip this step — PRs with build failures waste reviewer time.
# Clone the branch into a temp directory
TMPDIR=$(mktemp -d)
gh repo clone timescale/timescale-website "$TMPDIR" -- --branch {branch-name} --depth 1
# Install dependencies and run the build
cd "$TMPDIR"
npm install
npm run build
If the build fails:
- Read the error output carefully
- Fix the issue (missing imports, wrong const names, syntax errors, type errors)
- Commit the fix to the same branch (step 6)
- Run the build again until it passes
Common build failures to watch for:
- Undefined constants — you used a raw string instead of importing from
constants.ts - Missing imports — forgot to import a component or const you referenced
- Type errors — passed the wrong prop type to a component
- Module not found — wrong import path
Only proceed to opening the PR after the build succeeds. Clean up the temp directory when done:
rm -rf "$TMPDIR"
Cowork: This step cannot be performed in Cowork. Instead, after opening the PR, monitor the Vercel deployment status. If it fails, read the build logs and push a fix commit.
8. Open a PR
Claude Code:
gh pr create \
--repo timescale/timescale-website \
--base main \
--head {branch-name} \
--title "{short title}" \
--body "{description of changes}"
Cowork: Use the GitHub MCP create_pull_request tool:
create_pull_request(owner: "timescale", repo: "timescale-website", title: "{short title}", body: "{description of changes}", head: "{branch-name}", base: "main")
Include in the PR body:
- Summary of what changed
- Which page(s) are affected
9. Present the specific page preview
Append the page path to the preview URL so the user can jump directly to the changed page:
https://tigerdata-website-git-{branch-name}-tigerdata.vercel.app/{page-path}
For example, if the user changed the About page:
https://tigerdata-website-git-chore-update-about-copy-tigerdata.vercel.app/about
Tell the user:
- The Vercel preview takes 7-8 minutes to build after the PR is created
- Share the direct page preview URL so they can check the change visually
- The PR link so they can request reviews or merge when ready