diff --git a/axis-scenarios/ai-gateway/image-generation.ts b/axis-scenarios/ai-gateway/image-generation.ts index b16ae2c..ad0bd71 100644 --- a/axis-scenarios/ai-gateway/image-generation.ts +++ b/axis-scenarios/ai-gateway/image-generation.ts @@ -8,7 +8,7 @@ export default { judge: [ { check: "Uses the `@google/genai` package — image generation on the gateway is Gemini-only" }, { check: "Does NOT use `@google/generative-ai` (older deprecated package) and does NOT attempt OpenAI image models like dall-e or gpt-image" }, - { check: "Uses a Gemini image model (e.g. 'gemini-2.5-flash-image' or 'gemini-3-pro-image-preview')" }, + { check: "Uses a Gemini image model (e.g. 'gemini-2.5-flash-image' or 'gemini-3-pro-image')" }, { check: "Does NOT pass a custom apiKey or baseURL to the GoogleGenAI client — the gateway auto-configures it" }, { check: "Does NOT read or set `GEMINI_API_KEY` / `GOOGLE_API_KEY` — those disable the gateway" }, { check: "Extracts the image bytes from `response.candidates[0].content.parts[*].inlineData` (base64 string) rather than expecting a URL" }, diff --git a/axis-scenarios/ai-gateway/image-to-image.ts b/axis-scenarios/ai-gateway/image-to-image.ts index f4cbc95..312275d 100644 --- a/axis-scenarios/ai-gateway/image-to-image.ts +++ b/axis-scenarios/ai-gateway/image-to-image.ts @@ -16,7 +16,7 @@ export default { }, { check: - "Pins a Gemini IMAGE model (e.g. `gemini-2.5-flash-image`, `gemini-3.1-flash-image-preview`, or `gemini-3-pro-image-preview`).", + "Pins a Gemini IMAGE model (e.g. `gemini-2.5-flash-image`, `gemini-3.1-flash-image`, or `gemini-3-pro-image`).", }, { check: diff --git a/axis-scenarios/caching/tags-deploy-invalidation.ts b/axis-scenarios/caching/tags-deploy-invalidation.ts index 88b48e5..9ef231d 100644 --- a/axis-scenarios/caching/tags-deploy-invalidation.ts +++ b/axis-scenarios/caching/tags-deploy-invalidation.ts @@ -6,11 +6,12 @@ export default { prompt: "Create a Netlify function at /api/catalog whose JSON response is cached on Netlify's CDN and tagged so we can purge it on demand. Key requirement: a normal site deploy must NOT automatically wipe these cached responses — they should persist across deploys and only clear when we explicitly purge by tag. Also give me a separate /api/catalog/purge endpoint that does that explicit purge.", judge: [ - { check: "Tags the cached response using the `Netlify-Cache-Tag` header (or `Cache-Tag`) with at least one tag like 'catalog' — does NOT use `Netlify-Cache-ID`" }, - { check: "Relies on / explains the behavior that responses carrying a cache tag are excluded from automatic deploy-based invalidation — they persist across deploys and clear only on explicit purge" }, + { check: "Sets a `Netlify-Cache-ID` header on the cached response with a stable id like 'catalog' — this both opts the response out of automatic deploy-based invalidation AND registers the id as a purge tag" }, + { check: "Does NOT rely on `Netlify-Cache-Tag` to survive deploys — that header is purge-only and its responses are still wiped by automatic deploy-based invalidation" }, + { check: "Relies on / explains the behavior that a response carrying `Netlify-Cache-ID` is excluded from automatic deploy-based invalidation — it persists across deploys and clears only on explicit purge" }, { check: "Sets Netlify-CDN-Cache-Control with a meaningful s-maxage so the response is durably cached at the edge (not a near-zero TTL)" }, - { check: "The /api/catalog/purge endpoint calls purgeCache({ tags: ['catalog'] }) imported from '@netlify/functions', using the SAME tag string set on the response" }, - { check: "Does NOT propose triggering a new deploy or lowering the TTL as the invalidation mechanism — the requirement is explicit, targeted, deploy-surviving cache tags" }, + { check: "The /api/catalog/purge endpoint calls purgeCache({ tags: ['catalog'] }) imported from '@netlify/functions', using the SAME id string set as the response's `Netlify-Cache-ID` (the id doubles as a purge tag)" }, + { check: "Does NOT propose triggering a new deploy or lowering the TTL as the invalidation mechanism — the requirement is explicit, targeted, deploy-surviving caching" }, { check: "Both endpoints use the modern Netlify function signature with config.path set" }, ], variants: withSkillVariants(), diff --git a/axis-scenarios/config/functions-directory.ts b/axis-scenarios/config/functions-directory.ts index 451efa1..e0b5d74 100644 --- a/axis-scenarios/config/functions-directory.ts +++ b/axis-scenarios/config/functions-directory.ts @@ -9,7 +9,7 @@ export default { { check: "Adds a `[functions]` block with `directory = 'src/server/functions'`" }, { check: "Sets `node_bundler = 'esbuild'` inside the `[functions]` block" }, { check: "Does NOT leave the directory at the default `netlify/functions` — the user explicitly wants a custom path" }, - { check: "Uses the single-table `[functions]` form for these global settings (not a `[[functions]]` array-of-tables entry, which is for per-path routing)" }, + { check: "Uses the single-table `[functions]` form for these global settings — not a `[[functions]]` array-of-tables entry (no such construct exists in netlify.toml; per-function overrides use a `[functions.\"name-or-glob\"]` table)" }, { check: "Does NOT invent unrelated keys (e.g. a made-up `bundler`/`runtime` field) — only `directory` and `node_bundler` are needed" }, ], variants: withSkillVariants(), diff --git a/codex/skills/netlify-ai-gateway/SKILL.md b/codex/skills/netlify-ai-gateway/SKILL.md index 558d51e..4881130 100644 --- a/codex/skills/netlify-ai-gateway/SKILL.md +++ b/codex/skills/netlify-ai-gateway/SKILL.md @@ -112,7 +112,7 @@ export const config: Config = { ## Image Generation -Image generation on the gateway is supported through **Gemini image models** (e.g., `gemini-2.5-flash-image`, `gemini-3-pro-image-preview`, `gemini-3.1-flash-image-preview`). OpenAI's image models (`gpt-image-1`, `dall-e-*`) are **not** routed through the gateway. +Image generation on the gateway is supported through **Gemini image models** (e.g., `gemini-2.5-flash-image`, `gemini-3-pro-image`, `gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image`). OpenAI's image models (`gpt-image-1`, `dall-e-*`) are **not** routed through the gateway. Both text-to-image and image-to-image use the same `generateContent` method as chat — only the model and response shape differ. The image is returned as base64 `inlineData` on a content part, not as a URL. @@ -124,7 +124,7 @@ import { GoogleGenAI } from "@google/genai"; const ai = new GoogleGenAI({}); const response = await ai.models.generateContent({ - model: "gemini-3.1-flash-image-preview", + model: "gemini-3.1-flash-image", contents: "A watercolor portrait of a corgi wearing a beret", }); @@ -144,7 +144,7 @@ Pass the source image as an additional content part with `inlineData`: const sourceBase64 = sourceBuffer.toString("base64"); const response = await ai.models.generateContent({ - model: "gemini-3.1-flash-image-preview", + model: "gemini-3.1-flash-image", contents: [ { text: "Restyle this photo as a Picasso-era cubist portrait" }, { inlineData: { mimeType: "image/png", data: sourceBase64 } }, @@ -202,6 +202,6 @@ _Verified 2026-04-30 against the live AI Gateway providers list. The user-facing - Reasoning (o-series): `o3`, `o3-mini`, `o4-mini` ### Google Gemini (chat + image) -- Chat: `gemini-2.0-flash`, `gemini-2.0-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.5-pro`, `gemini-3-flash-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-pro-preview-customtools`, `gemini-flash-latest`, `gemini-flash-lite-latest` -- Image: `gemini-2.5-flash-image`, `gemini-3-pro-image-preview`, `gemini-3.1-flash-image-preview` +- Chat: `gemini-2.0-flash`, `gemini-2.0-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.5-pro`, `gemini-3-flash-preview`, `gemini-3.1-flash-lite`, `gemini-3.1-pro-preview`, `gemini-3.1-pro-preview-customtools`, `gemini-flash-latest`, `gemini-flash-lite-latest` +- Image: `gemini-2.5-flash-image`, `gemini-3-pro-image`, `gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image` diff --git a/codex/skills/netlify-caching/SKILL.md b/codex/skills/netlify-caching/SKILL.md index 1d87595..fb40cce 100644 --- a/codex/skills/netlify-caching/SKILL.md +++ b/codex/skills/netlify-caching/SKILL.md @@ -92,7 +92,27 @@ Purge entire site: await purgeCache(); ``` -Responses with `Netlify-Cache-Tag` are **excluded from automatic deploy-based invalidation** — they must be purged explicitly. +`Netlify-Cache-Tag` is **purge-only**: tagged responses are still cleared by automatic deploy-based invalidation like everything else. The tag only lets you purge them on demand between deploys. + +### Surviving deploys with `Netlify-Cache-ID` + +To keep a cached response *across* deploys, set a `Netlify-Cache-ID` header. A response carrying it is **excluded from automatic deploy-based invalidation** — it persists across deploys and clears only on explicit purge. The `Netlify-Cache-ID` value also auto-registers as a purge tag, so you purge it by that same id: + +```typescript +return new Response(body, { + headers: { + "Netlify-Cache-ID": "catalog", + "Netlify-CDN-Cache-Control": "public, s-maxage=86400", + }, +}); +``` + +```typescript +import { purgeCache } from "@netlify/functions"; + +// Purge by the same id — it doubles as a purge tag. +await purgeCache({ tags: ["catalog"] }); +``` ## Cache Key Variation @@ -123,10 +143,15 @@ Static assets are cached by default. API responses from Netlify Functions need e ## Debugging -Check the `Cache-Status` response header: -- `HIT` — served from cache -- `MISS` — generated fresh -- `REVALIDATED` — stale content was revalidated +Check the `Cache-Status` response header. Netlify emits it in the RFC 9211 format — one entry per named cache layer the request passed through, not a bare `HIT`/`MISS`: + +``` +Cache-Status: "Netlify Edge"; fwd=miss, "Netlify Durable"; hit; ttl=3600 +``` + +- A named layer (`"Netlify Edge"`, `"Netlify Durable"`) with `hit` — served from that cache +- `fwd=miss` — the entry was not in that layer, so the request was forwarded onward +- `ttl=…` — remaining freshness (seconds) for a hit ## Constraints diff --git a/codex/skills/netlify-cli-and-deploy/SKILL.md b/codex/skills/netlify-cli-and-deploy/SKILL.md index ffb627c..ff7e137 100644 --- a/codex/skills/netlify-cli-and-deploy/SKILL.md +++ b/codex/skills/netlify-cli-and-deploy/SKILL.md @@ -48,7 +48,7 @@ Site ID is stored in `.netlify/state.json`. Add `.netlify` to `.gitignore`. Set up with `netlify init`. Automatic deploys trigger on push/PR: - Push to production branch → production deploy - Open PR → deploy preview with unique URL -- Push to other branches → branch deploy +- Push to other branches → branch deploy **only if branch deploys are enabled** — they are off by default; turn them on (per branch or for all branches) in the site's build & deploy settings Build runs on Netlify's servers. Configure build settings in `netlify.toml`. diff --git a/codex/skills/netlify-deploy/references/netlify-toml.md b/codex/skills/netlify-deploy/references/netlify-toml.md index e2ee14d..0eb0fb8 100644 --- a/codex/skills/netlify-deploy/references/netlify-toml.md +++ b/codex/skills/netlify-deploy/references/netlify-toml.md @@ -19,7 +19,7 @@ Configuration file for Netlify builds and deployments. # Command to build your site command = "npm run build" - # Directory to publish (relative to repo root) + # Directory to publish (relative to base when base is set, otherwise repo root) publish = "dist" # Functions directory @@ -154,9 +154,9 @@ Deploy different settings per context: directory = "netlify/functions" node_bundler = "esbuild" -[[functions]] - path = "/api/*" - function = "api" +# Per-function overrides target a function by name or glob +[functions."my-function"] + included_files = ["data/**"] ``` ## Build Plugins diff --git a/codex/skills/netlify-forms/SKILL.md b/codex/skills/netlify-forms/SKILL.md index 8a7fc92..8cf8fbf 100644 --- a/codex/skills/netlify-forms/SKILL.md +++ b/codex/skills/netlify-forms/SKILL.md @@ -24,7 +24,9 @@ Netlify's build system detects the form and injects a hidden `form-name` input a ## JavaScript-Rendered Forms (React, Vue, SSR Frameworks) -For forms rendered by JavaScript frameworks (React, Vue, TanStack Start, Next.js, SvelteKit, Remix, Nuxt), Netlify's build parser cannot detect the form in static HTML. You MUST create a static HTML skeleton file for build-time form detection: +For forms rendered by JavaScript frameworks (React, Vue, Astro, TanStack Start, Next.js, SvelteKit, Remix, Nuxt), Netlify's build parser cannot detect the form in static HTML. You MUST create a static HTML skeleton file for build-time form detection: + +> **Form detection parses prerendered HTML only.** A `data-netlify` form is registered only if it appears in HTML produced at build time. On an Astro route that is server-rendered on demand (`export const prerender = false`, or an `output: "server"` route), the form is generated per request and is never scanned at build — so it is never registered. Put the form on a prerendered page, or add the static skeleton file below. Create a static HTML file in `public/` (e.g. `public/__forms.html`) containing a hidden copy of each form: diff --git a/codex/skills/netlify-frameworks/SKILL.md b/codex/skills/netlify-frameworks/SKILL.md index 65e46ed..d9f8d1a 100644 --- a/codex/skills/netlify-frameworks/SKILL.md +++ b/codex/skills/netlify-frameworks/SKILL.md @@ -21,7 +21,7 @@ Check these files to determine the framework: | `next.config.*` | Next.js | | `nuxt.config.*` | Nuxt | | `vite.config.*` + `react-router` | Vite + React (SPA or Remix) | -| `app.config.*` + `@tanstack/react-start` | TanStack Start | +| `vite.config.*` + `@tanstack/react-start` | TanStack Start | | `svelte.config.*` | SvelteKit | ## Framework Reference Guides diff --git a/codex/skills/netlify-functions/SKILL.md b/codex/skills/netlify-functions/SKILL.md index 38bc536..8e6e6d7 100644 --- a/codex/skills/netlify-functions/SKILL.md +++ b/codex/skills/netlify-functions/SKILL.md @@ -246,7 +246,7 @@ const apiKey = Netlify.env.get("API_KEY"); | Synchronous timeout | 60 seconds | | Background timeout | 15 minutes | | Scheduled timeout | 30 seconds | -| Memory | 1024 MB default; configurable 1024–4096 MB (see [Resource Configuration](#resource-configuration)) | +| Memory | 1024 MB default; configurable 1024–4096 MB (see [Memory or vCPU](#memory-or-vcpu)) | | Buffered payload | 6 MB | | Streamed payload | 20 MB | diff --git a/cursor/rules/netlify-ai-gateway.mdc b/cursor/rules/netlify-ai-gateway.mdc index 1b8c550..07e60cb 100644 --- a/cursor/rules/netlify-ai-gateway.mdc +++ b/cursor/rules/netlify-ai-gateway.mdc @@ -112,7 +112,7 @@ export const config: Config = { ## Image Generation -Image generation on the gateway is supported through **Gemini image models** (e.g., `gemini-2.5-flash-image`, `gemini-3-pro-image-preview`, `gemini-3.1-flash-image-preview`). OpenAI's image models (`gpt-image-1`, `dall-e-*`) are **not** routed through the gateway. +Image generation on the gateway is supported through **Gemini image models** (e.g., `gemini-2.5-flash-image`, `gemini-3-pro-image`, `gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image`). OpenAI's image models (`gpt-image-1`, `dall-e-*`) are **not** routed through the gateway. Both text-to-image and image-to-image use the same `generateContent` method as chat — only the model and response shape differ. The image is returned as base64 `inlineData` on a content part, not as a URL. @@ -124,7 +124,7 @@ import { GoogleGenAI } from "@google/genai"; const ai = new GoogleGenAI({}); const response = await ai.models.generateContent({ - model: "gemini-3.1-flash-image-preview", + model: "gemini-3.1-flash-image", contents: "A watercolor portrait of a corgi wearing a beret", }); @@ -144,7 +144,7 @@ Pass the source image as an additional content part with `inlineData`: const sourceBase64 = sourceBuffer.toString("base64"); const response = await ai.models.generateContent({ - model: "gemini-3.1-flash-image-preview", + model: "gemini-3.1-flash-image", contents: [ { text: "Restyle this photo as a Picasso-era cubist portrait" }, { inlineData: { mimeType: "image/png", data: sourceBase64 } }, @@ -202,5 +202,5 @@ _Verified 2026-04-30 against the live AI Gateway providers list. The user-facing - Reasoning (o-series): `o3`, `o3-mini`, `o4-mini` ### Google Gemini (chat + image) -- Chat: `gemini-2.0-flash`, `gemini-2.0-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.5-pro`, `gemini-3-flash-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-pro-preview-customtools`, `gemini-flash-latest`, `gemini-flash-lite-latest` -- Image: `gemini-2.5-flash-image`, `gemini-3-pro-image-preview`, `gemini-3.1-flash-image-preview` +- Chat: `gemini-2.0-flash`, `gemini-2.0-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.5-pro`, `gemini-3-flash-preview`, `gemini-3.1-flash-lite`, `gemini-3.1-pro-preview`, `gemini-3.1-pro-preview-customtools`, `gemini-flash-latest`, `gemini-flash-lite-latest` +- Image: `gemini-2.5-flash-image`, `gemini-3-pro-image`, `gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image` diff --git a/cursor/rules/netlify-caching.mdc b/cursor/rules/netlify-caching.mdc index 6573a7a..0850fcd 100644 --- a/cursor/rules/netlify-caching.mdc +++ b/cursor/rules/netlify-caching.mdc @@ -92,7 +92,27 @@ Purge entire site: await purgeCache(); ``` -Responses with `Netlify-Cache-Tag` are **excluded from automatic deploy-based invalidation** — they must be purged explicitly. +`Netlify-Cache-Tag` is **purge-only**: tagged responses are still cleared by automatic deploy-based invalidation like everything else. The tag only lets you purge them on demand between deploys. + +### Surviving deploys with `Netlify-Cache-ID` + +To keep a cached response *across* deploys, set a `Netlify-Cache-ID` header. A response carrying it is **excluded from automatic deploy-based invalidation** — it persists across deploys and clears only on explicit purge. The `Netlify-Cache-ID` value also auto-registers as a purge tag, so you purge it by that same id: + +```typescript +return new Response(body, { + headers: { + "Netlify-Cache-ID": "catalog", + "Netlify-CDN-Cache-Control": "public, s-maxage=86400", + }, +}); +``` + +```typescript +import { purgeCache } from "@netlify/functions"; + +// Purge by the same id — it doubles as a purge tag. +await purgeCache({ tags: ["catalog"] }); +``` ## Cache Key Variation @@ -123,10 +143,15 @@ Static assets are cached by default. API responses from Netlify Functions need e ## Debugging -Check the `Cache-Status` response header: -- `HIT` — served from cache -- `MISS` — generated fresh -- `REVALIDATED` — stale content was revalidated +Check the `Cache-Status` response header. Netlify emits it in the RFC 9211 format — one entry per named cache layer the request passed through, not a bare `HIT`/`MISS`: + +``` +Cache-Status: "Netlify Edge"; fwd=miss, "Netlify Durable"; hit; ttl=3600 +``` + +- A named layer (`"Netlify Edge"`, `"Netlify Durable"`) with `hit` — served from that cache +- `fwd=miss` — the entry was not in that layer, so the request was forwarded onward +- `ttl=…` — remaining freshness (seconds) for a hit ## Constraints diff --git a/cursor/rules/netlify-cli-and-deploy.mdc b/cursor/rules/netlify-cli-and-deploy.mdc index 3dece24..c6fc48e 100644 --- a/cursor/rules/netlify-cli-and-deploy.mdc +++ b/cursor/rules/netlify-cli-and-deploy.mdc @@ -48,7 +48,7 @@ Site ID is stored in `.netlify/state.json`. Add `.netlify` to `.gitignore`. Set up with `netlify init`. Automatic deploys trigger on push/PR: - Push to production branch → production deploy - Open PR → deploy preview with unique URL -- Push to other branches → branch deploy +- Push to other branches → branch deploy **only if branch deploys are enabled** — they are off by default; turn them on (per branch or for all branches) in the site's build & deploy settings Build runs on Netlify's servers. Configure build settings in `netlify.toml`. diff --git a/cursor/rules/netlify-deploy-netlify-toml.mdc b/cursor/rules/netlify-deploy-netlify-toml.mdc index 7cf4dd8..17436c9 100644 --- a/cursor/rules/netlify-deploy-netlify-toml.mdc +++ b/cursor/rules/netlify-deploy-netlify-toml.mdc @@ -23,7 +23,7 @@ Configuration file for Netlify builds and deployments. # Command to build your site command = "npm run build" - # Directory to publish (relative to repo root) + # Directory to publish (relative to base when base is set, otherwise repo root) publish = "dist" # Functions directory @@ -158,9 +158,9 @@ Deploy different settings per context: directory = "netlify/functions" node_bundler = "esbuild" -[[functions]] - path = "/api/*" - function = "api" +# Per-function overrides target a function by name or glob +[functions."my-function"] + included_files = ["data/**"] ``` ## Build Plugins diff --git a/cursor/rules/netlify-forms.mdc b/cursor/rules/netlify-forms.mdc index e3a40c0..beceea8 100644 --- a/cursor/rules/netlify-forms.mdc +++ b/cursor/rules/netlify-forms.mdc @@ -24,7 +24,9 @@ Netlify's build system detects the form and injects a hidden `form-name` input a ## JavaScript-Rendered Forms (React, Vue, SSR Frameworks) -For forms rendered by JavaScript frameworks (React, Vue, TanStack Start, Next.js, SvelteKit, Remix, Nuxt), Netlify's build parser cannot detect the form in static HTML. You MUST create a static HTML skeleton file for build-time form detection: +For forms rendered by JavaScript frameworks (React, Vue, Astro, TanStack Start, Next.js, SvelteKit, Remix, Nuxt), Netlify's build parser cannot detect the form in static HTML. You MUST create a static HTML skeleton file for build-time form detection: + +> **Form detection parses prerendered HTML only.** A `data-netlify` form is registered only if it appears in HTML produced at build time. On an Astro route that is server-rendered on demand (`export const prerender = false`, or an `output: "server"` route), the form is generated per request and is never scanned at build — so it is never registered. Put the form on a prerendered page, or add the static skeleton file below. Create a static HTML file in `public/` (e.g. `public/__forms.html`) containing a hidden copy of each form: diff --git a/cursor/rules/netlify-frameworks.mdc b/cursor/rules/netlify-frameworks.mdc index bef16b5..d8c1a88 100644 --- a/cursor/rules/netlify-frameworks.mdc +++ b/cursor/rules/netlify-frameworks.mdc @@ -21,7 +21,7 @@ Check these files to determine the framework: | `next.config.*` | Next.js | | `nuxt.config.*` | Nuxt | | `vite.config.*` + `react-router` | Vite + React (SPA or Remix) | -| `app.config.*` + `@tanstack/react-start` | TanStack Start | +| `vite.config.*` + `@tanstack/react-start` | TanStack Start | | `svelte.config.*` | SvelteKit | ## Framework Reference Guides diff --git a/cursor/rules/netlify-functions.mdc b/cursor/rules/netlify-functions.mdc index d1cf7a5..b0df7e3 100644 --- a/cursor/rules/netlify-functions.mdc +++ b/cursor/rules/netlify-functions.mdc @@ -246,7 +246,7 @@ const apiKey = Netlify.env.get("API_KEY"); | Synchronous timeout | 60 seconds | | Background timeout | 15 minutes | | Scheduled timeout | 30 seconds | -| Memory | 1024 MB default; configurable 1024–4096 MB (see [Resource Configuration](#resource-configuration)) | +| Memory | 1024 MB default; configurable 1024–4096 MB (see [Memory or vCPU](#memory-or-vcpu)) | | Buffered payload | 6 MB | | Streamed payload | 20 MB | diff --git a/skills/netlify-ai-gateway/SKILL.md b/skills/netlify-ai-gateway/SKILL.md index 558d51e..4881130 100644 --- a/skills/netlify-ai-gateway/SKILL.md +++ b/skills/netlify-ai-gateway/SKILL.md @@ -112,7 +112,7 @@ export const config: Config = { ## Image Generation -Image generation on the gateway is supported through **Gemini image models** (e.g., `gemini-2.5-flash-image`, `gemini-3-pro-image-preview`, `gemini-3.1-flash-image-preview`). OpenAI's image models (`gpt-image-1`, `dall-e-*`) are **not** routed through the gateway. +Image generation on the gateway is supported through **Gemini image models** (e.g., `gemini-2.5-flash-image`, `gemini-3-pro-image`, `gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image`). OpenAI's image models (`gpt-image-1`, `dall-e-*`) are **not** routed through the gateway. Both text-to-image and image-to-image use the same `generateContent` method as chat — only the model and response shape differ. The image is returned as base64 `inlineData` on a content part, not as a URL. @@ -124,7 +124,7 @@ import { GoogleGenAI } from "@google/genai"; const ai = new GoogleGenAI({}); const response = await ai.models.generateContent({ - model: "gemini-3.1-flash-image-preview", + model: "gemini-3.1-flash-image", contents: "A watercolor portrait of a corgi wearing a beret", }); @@ -144,7 +144,7 @@ Pass the source image as an additional content part with `inlineData`: const sourceBase64 = sourceBuffer.toString("base64"); const response = await ai.models.generateContent({ - model: "gemini-3.1-flash-image-preview", + model: "gemini-3.1-flash-image", contents: [ { text: "Restyle this photo as a Picasso-era cubist portrait" }, { inlineData: { mimeType: "image/png", data: sourceBase64 } }, @@ -202,6 +202,6 @@ _Verified 2026-04-30 against the live AI Gateway providers list. The user-facing - Reasoning (o-series): `o3`, `o3-mini`, `o4-mini` ### Google Gemini (chat + image) -- Chat: `gemini-2.0-flash`, `gemini-2.0-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.5-pro`, `gemini-3-flash-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3.1-pro-preview`, `gemini-3.1-pro-preview-customtools`, `gemini-flash-latest`, `gemini-flash-lite-latest` -- Image: `gemini-2.5-flash-image`, `gemini-3-pro-image-preview`, `gemini-3.1-flash-image-preview` +- Chat: `gemini-2.0-flash`, `gemini-2.0-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.5-pro`, `gemini-3-flash-preview`, `gemini-3.1-flash-lite`, `gemini-3.1-pro-preview`, `gemini-3.1-pro-preview-customtools`, `gemini-flash-latest`, `gemini-flash-lite-latest` +- Image: `gemini-2.5-flash-image`, `gemini-3-pro-image`, `gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image` diff --git a/skills/netlify-caching/SKILL.md b/skills/netlify-caching/SKILL.md index 1d87595..fb40cce 100644 --- a/skills/netlify-caching/SKILL.md +++ b/skills/netlify-caching/SKILL.md @@ -92,7 +92,27 @@ Purge entire site: await purgeCache(); ``` -Responses with `Netlify-Cache-Tag` are **excluded from automatic deploy-based invalidation** — they must be purged explicitly. +`Netlify-Cache-Tag` is **purge-only**: tagged responses are still cleared by automatic deploy-based invalidation like everything else. The tag only lets you purge them on demand between deploys. + +### Surviving deploys with `Netlify-Cache-ID` + +To keep a cached response *across* deploys, set a `Netlify-Cache-ID` header. A response carrying it is **excluded from automatic deploy-based invalidation** — it persists across deploys and clears only on explicit purge. The `Netlify-Cache-ID` value also auto-registers as a purge tag, so you purge it by that same id: + +```typescript +return new Response(body, { + headers: { + "Netlify-Cache-ID": "catalog", + "Netlify-CDN-Cache-Control": "public, s-maxage=86400", + }, +}); +``` + +```typescript +import { purgeCache } from "@netlify/functions"; + +// Purge by the same id — it doubles as a purge tag. +await purgeCache({ tags: ["catalog"] }); +``` ## Cache Key Variation @@ -123,10 +143,15 @@ Static assets are cached by default. API responses from Netlify Functions need e ## Debugging -Check the `Cache-Status` response header: -- `HIT` — served from cache -- `MISS` — generated fresh -- `REVALIDATED` — stale content was revalidated +Check the `Cache-Status` response header. Netlify emits it in the RFC 9211 format — one entry per named cache layer the request passed through, not a bare `HIT`/`MISS`: + +``` +Cache-Status: "Netlify Edge"; fwd=miss, "Netlify Durable"; hit; ttl=3600 +``` + +- A named layer (`"Netlify Edge"`, `"Netlify Durable"`) with `hit` — served from that cache +- `fwd=miss` — the entry was not in that layer, so the request was forwarded onward +- `ttl=…` — remaining freshness (seconds) for a hit ## Constraints diff --git a/skills/netlify-cli-and-deploy/SKILL.md b/skills/netlify-cli-and-deploy/SKILL.md index ffb627c..ff7e137 100644 --- a/skills/netlify-cli-and-deploy/SKILL.md +++ b/skills/netlify-cli-and-deploy/SKILL.md @@ -48,7 +48,7 @@ Site ID is stored in `.netlify/state.json`. Add `.netlify` to `.gitignore`. Set up with `netlify init`. Automatic deploys trigger on push/PR: - Push to production branch → production deploy - Open PR → deploy preview with unique URL -- Push to other branches → branch deploy +- Push to other branches → branch deploy **only if branch deploys are enabled** — they are off by default; turn them on (per branch or for all branches) in the site's build & deploy settings Build runs on Netlify's servers. Configure build settings in `netlify.toml`. diff --git a/skills/netlify-deploy/references/netlify-toml.md b/skills/netlify-deploy/references/netlify-toml.md index e2ee14d..0eb0fb8 100644 --- a/skills/netlify-deploy/references/netlify-toml.md +++ b/skills/netlify-deploy/references/netlify-toml.md @@ -19,7 +19,7 @@ Configuration file for Netlify builds and deployments. # Command to build your site command = "npm run build" - # Directory to publish (relative to repo root) + # Directory to publish (relative to base when base is set, otherwise repo root) publish = "dist" # Functions directory @@ -154,9 +154,9 @@ Deploy different settings per context: directory = "netlify/functions" node_bundler = "esbuild" -[[functions]] - path = "/api/*" - function = "api" +# Per-function overrides target a function by name or glob +[functions."my-function"] + included_files = ["data/**"] ``` ## Build Plugins diff --git a/skills/netlify-forms/SKILL.md b/skills/netlify-forms/SKILL.md index 8a7fc92..8cf8fbf 100644 --- a/skills/netlify-forms/SKILL.md +++ b/skills/netlify-forms/SKILL.md @@ -24,7 +24,9 @@ Netlify's build system detects the form and injects a hidden `form-name` input a ## JavaScript-Rendered Forms (React, Vue, SSR Frameworks) -For forms rendered by JavaScript frameworks (React, Vue, TanStack Start, Next.js, SvelteKit, Remix, Nuxt), Netlify's build parser cannot detect the form in static HTML. You MUST create a static HTML skeleton file for build-time form detection: +For forms rendered by JavaScript frameworks (React, Vue, Astro, TanStack Start, Next.js, SvelteKit, Remix, Nuxt), Netlify's build parser cannot detect the form in static HTML. You MUST create a static HTML skeleton file for build-time form detection: + +> **Form detection parses prerendered HTML only.** A `data-netlify` form is registered only if it appears in HTML produced at build time. On an Astro route that is server-rendered on demand (`export const prerender = false`, or an `output: "server"` route), the form is generated per request and is never scanned at build — so it is never registered. Put the form on a prerendered page, or add the static skeleton file below. Create a static HTML file in `public/` (e.g. `public/__forms.html`) containing a hidden copy of each form: diff --git a/skills/netlify-frameworks/SKILL.md b/skills/netlify-frameworks/SKILL.md index 65e46ed..d9f8d1a 100644 --- a/skills/netlify-frameworks/SKILL.md +++ b/skills/netlify-frameworks/SKILL.md @@ -21,7 +21,7 @@ Check these files to determine the framework: | `next.config.*` | Next.js | | `nuxt.config.*` | Nuxt | | `vite.config.*` + `react-router` | Vite + React (SPA or Remix) | -| `app.config.*` + `@tanstack/react-start` | TanStack Start | +| `vite.config.*` + `@tanstack/react-start` | TanStack Start | | `svelte.config.*` | SvelteKit | ## Framework Reference Guides diff --git a/skills/netlify-functions/SKILL.md b/skills/netlify-functions/SKILL.md index 38bc536..8e6e6d7 100644 --- a/skills/netlify-functions/SKILL.md +++ b/skills/netlify-functions/SKILL.md @@ -246,7 +246,7 @@ const apiKey = Netlify.env.get("API_KEY"); | Synchronous timeout | 60 seconds | | Background timeout | 15 minutes | | Scheduled timeout | 30 seconds | -| Memory | 1024 MB default; configurable 1024–4096 MB (see [Resource Configuration](#resource-configuration)) | +| Memory | 1024 MB default; configurable 1024–4096 MB (see [Memory or vCPU](#memory-or-vcpu)) | | Buffered payload | 6 MB | | Streamed payload | 20 MB |