Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ NUXT_HEALTH_CHECK_NOTIFY_TO=
NUXT_PUBLIC_SITE_URL=
NUXT_OAUTH_GITHUB_CLIENT_ID=
NUXT_OAUTH_GITHUB_CLIENT_SECRET=
NUXT_OG_IMAGE_SECRET=
# Signs OG image URLs. At least 32 characters: pnpm exec nuxt-og-image generate-secret
NUXT_APP_SECRET=
# Signs same-origin image proxy addresses. Keep it separate from NUXT_TOKEN_KEY.
NUXT_IMAGE_PROXY_KEY=
NUXT_AI_READY_RUNTIME_SYNC_SECRET=
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/deploy-cloudflare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:

# Persists + accumulates prior deploys' client chunks across builds so old
# browser sessions still resolve their old _nuxt chunks during a rollout
# (nuxt-skew-protection, fs storage). Keyed on the SHA so every build saves
# (nuxt-skew-protection storage). Keyed on the SHA so every build saves
# a fresh entry; the prefix restore-key restores the previous accumulated
# set. Mirrors nuxtseo.com/apps/site.
- name: Cache skew-protection assets
Expand All @@ -86,7 +86,6 @@ jobs:
env:
NODE_OPTIONS: --max-old-space-size=8192
NUXT_SESSION_PASSWORD: ${{ secrets.NUXT_SESSION_PASSWORD }}
NUXT_OG_IMAGE_SECRET: ${{ secrets.NUXT_OG_IMAGE_SECRET }}
NUXT_AI_READY_RUNTIME_SYNC_SECRET: ${{ secrets.NUXT_AI_READY_RUNTIME_SYNC_SECRET }}
# Enables Sentry source-map upload + release injection so client errors
# are un-minified and tagged with a release (fixes release: None on SKILLD-1).
Expand Down
5 changes: 4 additions & 1 deletion layers/registry/server/api/__sitemap__/retired.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SitemapUrlInput } from '#sitemap/types'
import { GONE_SKILLS_SQL } from '~~/server/utils/source-gone-skills'
import { getDB } from '#server/utils/db'
import retiredUrls from '../../data/retired-urls.json'
Expand All @@ -7,7 +8,9 @@ import { MARKETING_REDIRECTS } from '../../utils/tag-quality'
// Temporary sitemap of retired URLs. Experiment E, remove 2026-11-11. The
// reasons, the status guarantee and the removal steps are in
// `../../utils/retired-sitemap.ts`.
export default defineSitemapEventHandler(async (event) => {
// h3 `defineEventHandler`: the helpers below read the Nitro event, and
// `defineSitemapEventHandler` types its argument as the narrower `RequestEvent`.
export default defineEventHandler(async (event): Promise<SitemapUrlInput[]> => {
// Past the removal date, skip the D1 queries too.
const now = new Date()
if (!isRetiredSitemapActive(now))
Expand Down
5 changes: 4 additions & 1 deletion layers/registry/server/api/__sitemap__/skills.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SitemapUrlInput } from '#sitemap/types'
import { getDB } from '#server/utils/db'
import { cached } from '#shared/server/cache'
import { runAfterResponse } from '../../utils/after-response'
Expand All @@ -12,7 +13,9 @@ import { listTrendingSitemapEntries } from '../../utils/trending-sitemap'
* rule (`trending-admission.ts`) that the Skill API applies to the page. The
* trending boards join them, so a crawler can find the admitted set.
*/
export default defineSitemapEventHandler(async (event) => {
// h3 `defineEventHandler`: the helpers below read the Nitro event, and
// `defineSitemapEventHandler` types its argument as the narrower `RequestEvent`.
export default defineEventHandler(async (event): Promise<SitemapUrlInput[]> => {
const skills = await listAllSkillsForSitemap(event)
const boards = await cached({
storage: useStorage('cache'),
Expand Down
22 changes: 3 additions & 19 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,6 @@ export default defineNuxtConfig({
modules: [
'@harlan-zw/nuxt-checkin',
'@harlan-zw/nuxt-cf-jobs',
// Before nuxt-cloudflare, on purpose. Both check each HTML response in
// `beforeResponse`, in module order. nuxt-skew-protection has to drop its
// `__nkpv` version cookie from a document a shared cache may keep before
// nuxt-cloudflare looks, or nuxt-cloudflare sees the cookie and marks every
// browser navigation `no-store`, so only crawlers would ever fill the cache.
'nuxt-skew-protection',
'@harlan-zw/nuxt-cloudflare',
'@harlan-zw/nuxt-dx',
'@harlan-zw/nuxt-use-query',
Expand All @@ -104,7 +98,6 @@ export default defineNuxtConfig({
'@nuxt/fonts',
'@nuxt/scripts',
'@nuxtjs/seo',
'nuxt-ai-ready',
'@nuxtjs/mcp-toolkit',
'@nuxt/a11y',
'@nuxtjs/html-validator',
Expand Down Expand Up @@ -238,7 +231,7 @@ export default defineNuxtConfig({

// Version skew protection: after a deploy, clients still running the previous
// build fetch old hashed chunks. Instead of 404ing (Sentry SKILLD-4), prior
// builds' assets are retained (default `fs` storage under
// builds' assets are retained (default storage under
// node_modules/.cache/nuxt-seo) and bundled into the new deployment so the
// Worker serves them as static assets. CI persists that cache dir across
// deploys via actions/cache (see deploy-cloudflare.yml). Mirrors
Expand All @@ -265,8 +258,8 @@ export default defineNuxtConfig({
// same unit the cache is, which is seconds.
//
// The module publishes its retention window (36000s) to
// `@harlan-zw/nuxt-cloudflare`, which honours an HTML cache rule up to it,
// and drops the version cookie from any document a shared cache may keep.
// `@harlan-zw/nuxt-cloudflare`, which honours an HTML cache rule up to it.
// Nuxt SEO turns the version cookie off, so no document carries one.
// The shell renders signed out for everyone, so `/skills/trending` has the
// first rule. Skill, repo and category pages follow once it holds.
},
Expand Down Expand Up @@ -532,15 +525,6 @@ export default defineNuxtConfig({
},
},

ogImage: {
security: {
// Production gets its stable key from NUXT_OG_IMAGE_SECRET. A fixed,
// non-sensitive local key keeps signed dev URLs valid across HMR builds.
secret: process.env.NUXT_OG_IMAGE_SECRET
|| (process.env.NODE_ENV === 'development' ? 'skilld-local-development' : undefined),
},
},

// SWR caching only kicks in for production builds; in dev every request
// re-renders so HMR isn't fighting a stale cached HTML/JSON response.
// SWR disabled: stale-while-revalidate kept serving stale content after deploys
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,14 @@
"agents": "catalog:",
"comark": "catalog:",
"diff": "catalog:",
"drizzle-orm": "catalog:",
"fflate": "catalog:",
"h3": "catalog:",
"import-in-the-middle": "catalog:",
"marked": "catalog:",
"motion-v": "catalog:",
"nuxt": "catalog:",
"nuxt-ai-ready": "catalog:",
"nuxt-auth-utils": "catalog:",
"nuxt-og-image": "catalog:",
"rangi": "catalog:",
"reka-ui": "catalog:",
"skilld-protocol": "catalog:",
Expand Down Expand Up @@ -121,7 +120,7 @@
"eslint-plugin-harlanzw": "catalog:",
"happy-dom": "catalog:",
"lint-staged": "catalog:",
"nuxt-skew-protection": "catalog:",
"nuxtseo-devtools-host": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:",
Expand Down
Loading
Loading