This document provides guidelines for agentic coding agents operating in the Sink codebase.
Sink is a simple, speedy, and secure link shortener with analytics, running 100% on Cloudflare. It uses Nuxt 4 as the frontend framework and Cloudflare Workers for serverless backend.
All documentation and comments within this project must be written in English.
app/ # Nuxt 4 application (pages, layouts, components, composables)
├── components/ # Vue components (PascalCase)
│ ├── ui/ # shadcn-vue base components (auto-generated, do not edit)
│ ├── dashboard/ # Dashboard-specific components
│ └── home/ # Landing page components
├── composables/ # Vue composables (camelCase)
├── layouts/ # Nuxt layouts
├── pages/ # File-based routing
├── stores/ # Pinia stores
├── types/ # TypeScript type definitions
├── utils/ # Utility functions
├── lib/ # Shared library helpers
└── assets/ # CSS, images
server/ # Nitro server (Cloudflare Workers)
├── api/ # API endpoints
├── middleware/ # Server middleware
└── utils/ # Server utilities
schemas/ # Zod schemas for validation
tests/ # Vitest tests
scripts/ # Build scripts
docs/ # Documentation
public/ # Static assets
i18n/ # Internationalization files
Use pnpm (v10+) with Node.js 20.11+.
pnpm dev # Start Nuxt dev server on port 7465
pnpm preview # Full Worker preview via wrangler devpnpm build # Production build (nuxt build + map generator)
pnpm build:map # Generate country map data
pnpm build:colo # Generate Cloudflare colo datapnpm lint:fix # Run ESLint with auto-fix
pnpm types:check # TypeScript type checkingpnpm deploy:pages # Deploy to Cloudflare Pages
pnpm deploy:worker # Deploy to Cloudflare WorkersThis project uses @antfu/eslint-config with additional Tailwind CSS linting.
- Indentation: 2 spaces
- Quotes: Single quotes for strings
- Semicolons: No semicolons
- Trailing commas: Always use trailing commas
- Line length: No strict limit, but keep readable
- Use TypeScript for all code
- Prefer
interfacefor object types,typefor unions/intersections - Avoid
any; use proper typing orunknownwhen necessary - Use Zod schemas for runtime validation (see
schemas/)
// Good
interface Link {
id: string
url: string
slug: string
}
// Zod schema example
export const LinkSchema = z.object({
id: z.string().trim().max(26),
url: z.string().trim().url().max(2048),
slug: z.string().trim().max(2048).regex(new RegExp(slugRegex)),
})- Use
<script setup lang="ts">for all components - Component files use PascalCase:
LinkEditor.vue - Props defined with
defineProps<{ ... }>() - Emits defined with
defineEmits<{ ... }>()
<script setup lang="ts">
import type { Link } from '@/types'
const props = defineProps<{
link: Link
}>()
const emit = defineEmits<{
update: [link: Link]
}>()
</script>
<template>
<!-- Template here -->
<div>{{ props.link.slug }}</div>
</template>- Prefer Nuxt auto-imports for Vue utilities, composables, and components
- Auto-imported:
ref,computed,watch,useRoute,useFetch,defineStore, etc. - Explicit imports for:
- External libraries:
import { z } from 'zod' - Types:
import type { Link } from '@/types' - Icons:
import { Copy, Link } from 'lucide-vue-next' - Path aliases:
@/(app),@@/(root)
- External libraries:
| Item | Convention | Example |
|---|---|---|
| Components | PascalCase | LinkEditor.vue |
| Composables | camelCase with use prefix |
useDashboardRoute() |
| Stores | camelCase with use...Store |
useDashboardLinksStore |
| API routes | kebab-case with method suffix | create.post.ts |
| Directories | kebab-case | dashboard/links/ |
| Functions/variables | camelCase | getLink, shortLink |
| Constants | UPPER_SNAKE_CASE | DASHBOARD_ROUTES |
- Use
createError()for API errors with proper status codes - Validate inputs with Zod schemas and
readValidatedBody()
// Server API example
export default eventHandler(async (event) => {
const link = await readValidatedBody(event, LinkSchema.parse)
if (existingLink) {
throw createError({
status: 409,
statusText: 'Link already exists',
})
}
})- Use shadcn-vue components from
app/components/ui/ - Do not edit files in
components/ui/directly (auto-generated) - Add custom components to appropriate subdirectories
- Use Tailwind CSS v4 for styling
Defined in wrangler.jsonc:
KV: Workers KV for link storageANALYTICS: Analytics Engine for trackingAI: Workers AI for slug generationASSETS: Static assets binding
Access in server code via event.context.cloudflare.env:
const { KV, ANALYTICS, AI } = event.context.cloudflare.envKey variables (set in .env):
NUXT_SITE_TOKEN: Authentication tokenNUXT_REDIRECT_STATUS_CODE: HTTP redirect code (default: 301)NUXT_LINK_CACHE_TTL: Cache TTL in secondsNUXT_AI_MODEL: AI model for slug generation
See docs/configuration.md for full list.
Follow Conventional Commits:
feat: add link expiration feature
fix: correct analytics date filter
docs: update API documentation
chore(deps): bump dependencies
refactor: simplify link store logic
simple-git-hooksrunslint-stagedon commitlint-stagedrunseslint --fixon staged.js,.ts,.tsx,.vuefiles- Always run
pnpm lint:fixbefore committing