This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
npm run dev # Start dev server on port 3030
npm run build # Build static site to /out (includes sitemap generation)
npm run preview # Build and serve locallyThis is a Next.js 14 site using both App Router and Pages Router simultaneously:
- App Router (
/app): Homepage, blog, landing pages, guides, templates - Pages Router (
/pages/docs): Documentation powered by Nextra
The site uses static export (output: 'export') - all pages pre-render to /out.
app/- App Router pages (homepage atpage.tsx, blog atblog/[slug]/)pages/docs/- Nextra documentation (MDX files with_meta.jsonfor navigation)components/site/- Shared Header/Footercomponents/ui/- Client components (MobileNav, DownloadButton)content/blog/- Blog posts in MDX formatcontent/blog/images/- Blog image generation script (images themselves are gitignored)lib/- Utilities (blog.ts for post loading)
TypeScript path aliases configured in tsconfig.json:
@/*→ root@/components/*,@/lib/*,@/content/*,@/app/*
- Tailwind CSS 4.x with custom config
- Neon cyan accent:
#4de1ff(CSS variable--color-neon) - Global styles in
app/globals.css
Create MDX files in content/blog/ with frontmatter:
---
title: "Post Title"
date: "2026-01-21"
slug: "post-slug"
id: "uuid"
image:
url: "https://dev-tools-blog-images.fly.storage.tigris.dev/blog/post-slug.webp"
alt: "Post Title"
author:
name: "DevTools Team"
avatar: "/logo.svg"
tags: ["tag1", "tag2"]
category: "Guides"
summary: "Brief summary"
cta:
primary:
heading: "CTA heading"
body: "CTA body"
buttonText: "Button"
url: "https://dev.tools/..."
secondaryText: "Secondary"
secondaryUrl: "https://dev.tools/..."
secondary:
heading: "Secondary CTA"
body: "Body"
buttonText: "Button"
url: "https://dev.tools/..."
secondaryText: "Secondary"
secondaryUrl: "https://dev.tools/..."
badge:
text: "Badge"
variant: "neutral"
---When writing DevTools YAML flow examples in blog posts, use the real DevTools format. Reference docs:
- Variable syntax:
pages/docs/how-to/environments-and-variables.md - Flow format:
pages/docs/how-to/working-with-flows.md
Key rules:
- Variables:
{{VARIABLE_NAME}}(double curly braces) — NOT${VARIABLE_NAME} - Node output refs:
{{NodeName.response.body.field}}for request outputs,{{JsNodeName.field}}for JS outputs - Environment refs:
{{#env:VAR_NAME}}in variable values — NOT bare#env:VAR_NAME - Built-in functions:
{{ uuid() }},{{ now() }} - No
extract:,capture:, orassert:blocks — these don't exist in the real format - Assertions: Use
js:nodes thatthrow new Error()on failure - Step types:
request:,js:,if:,for:,for_each:— each withname:inside - Execution order:
depends_on: NodeNamefor explicit ordering
Example structure:
workspace_name: My Workspace
env:
BASE_URL: '{{BASE_URL}}'
run:
- flow: MyFlow
flows:
- name: MyFlow
steps:
- request:
name: Login
method: POST
url: '{{BASE_URL}}/auth/login'
headers:
Content-Type: application/json
body:
email: 'test@example.com'
- js:
name: ValidateLogin
code: |
export default function(ctx) {
if (ctx.Login?.response?.status !== 200) throw new Error("Failed");
return { validated: true };
}
depends_on: Login
- request:
name: GetData
method: GET
url: '{{BASE_URL}}/data'
headers:
Authorization: 'Bearer {{Login.response.body.access_token}}'
depends_on: LoginGenerate and upload blog header images using the Nano Banana (Gemini) API:
bash content/blog/images/generate.sh <name> "<prompt>"- Generates PNG via Gemini API, converts to optimized webp (cwebp q80, 1456w), uploads to Tigris
- Requires
.env.localwithGEMINI_API_KEYand Tigris AWS credentials - Images are hosted on Tigris:
https://dev-tools-blog-images.fly.storage.tigris.dev/blog/<name>.webp - Match existing image style: photorealistic editorial stock photography of developers at work (office desks, laptops with relevant screen content, notebooks, coffee mugs, whiteboards, warm natural lighting, 16:9 landscape)
- Config in
theme.config.tsx - Navigation controlled by
_meta.jsonfiles inpages/docs/ - Docs repository base:
https://github.com/the-dev-tools/dev-tools-website/blob/main/pages/docs
- Auto-deploys to Fly.io on push to master
- Static files served via Caddy (see
Caddyfile) - Docker build defined in
Dockerfile - Blog images hosted on Tigris (Fly.io object storage), bucket:
dev-tools-blog-images
All secrets live in .env.local (gitignored). Keys:
GEMINI_API_KEY— Gemini Nano Banana API for image generationAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_ENDPOINT_URL_S3,AWS_REGION,BUCKET_NAME— Tigris object storage