diff --git a/.agents/skills/seo/SKILL.md b/.agents/skills/seo/SKILL.md new file mode 100644 index 0000000000..d9572a5321 --- /dev/null +++ b/.agents/skills/seo/SKILL.md @@ -0,0 +1,237 @@ +--- +name: seo +description: > + SEO best practices for the Grida project across Next.js pages, blog posts, + and documentation. Covers Next.js metadata API, Open Graph / Twitter cards, + sitemaps, image search optimization, structured data (JSON-LD), Docusaurus + frontmatter, and content writing for search. Use when creating or editing + public-facing pages under editor/app/(www), writing blog posts, authoring + docs, or reviewing SEO-related metadata. Trigger phrases: "SEO", "metadata", + "sitemap", "open graph", "og image", "meta tags", "search ranking", + "structured data", "JSON-LD". +--- + +# SEO + +Guidelines for search engine optimization across the Grida project -- Next.js +pages, blog posts, and documentation. + +## Scope + +| Surface | Location | Framework | +| ------------------------- | --------------------------------- | ---------- | +| Marketing / product pages | `editor/app/(www)/` | Next.js 16 | +| Blog | `docs/blog/` or `apps/docs/blog/` | Docusaurus | +| Documentation | `docs/**` | Docusaurus | +| Sitemaps | `editor/app/sitemap.ts` + others | Next.js | + +## Next.js Pages (`editor/app/(www)`) + +### Metadata + +Every public page must export metadata (static or dynamic). + +```tsx +// Static +export const metadata: Metadata = { + title: "Page Title — Grida", + description: "Concise, keyword-rich description under 160 chars.", + keywords: ["relevant", "keywords"], + openGraph: { + title: "Page Title — Grida", + description: "Same or tailored OG description.", + images: ["/og/page-name.png"], + }, + twitter: { + card: "summary_large_image", + }, +}; + +// Dynamic +export async function generateMetadata({ params }): Promise { ... } +``` + +**Rules:** + +- `title` should end with ` — Grida` (or use a `title.template` in the root layout). +- `description` must be unique per page, under 160 characters, and include primary keywords. +- Every page with OG images must set `metadataBase` or use absolute URLs so crawlers resolve them correctly. +- OG images must return **200** without auth and use correct `Content-Type`. + +### `metadataBase` + +When `openGraph.images` uses relative paths, the layout or page must set: + +```tsx +metadataBase: new URL("https://grida.co"), +``` + +Without this, crawlers may see broken OG URLs. + +### Sitemap + +Public pages belong in `editor/app/sitemap.ts`. + +```ts +{ + url: "https://grida.co/new-page", + changeFrequency: "monthly", + priority: 0.5, +} +``` + +- Use `priority: 1` only for the homepage. +- Match `changeFrequency` to actual update cadence. +- When adding a new public page, always add a sitemap entry. + +### Google Image Search + +Next.js `` renders optimized `/_next/image?url=...` URLs that Google +Images indexes unreliably. + +**For pages where images are the search target** (brand assets, logos, press +kits): + +- Use `` or plain `` so the rendered `src` is a + stable, direct public URL (e.g. `/brand/grida-symbol-240.png`). +- Include descriptive `alt` text with "Grida" + asset name + format. + +**Checklist for image assets:** + +- Returns **200** without cookies; not blocked for `Googlebot-Image` +- Correct `Content-Type` (`image/png`, `image/svg+xml`, etc.) +- Not blocked by `robots.txt` or `X-Robots-Tag` +- `/_next/image` endpoint itself is not blocked (other pages still use it) +- Stable, clean URLs under `public/` -- no query-string-only canonicals + +### Structured Data (JSON-LD) + +Use JSON-LD for rich results where applicable: + +- **Product pages**: `Product` or `SoftwareApplication` +- **Blog posts**: `BlogPosting` or `Article` +- **FAQ sections**: `FAQPage` +- **Brand/org**: `Organization` + +Embed via a `