From 365b387a53c209ba3e72a0832320327eb2db7e8d Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Thu, 27 Aug 2026 11:45:49 +0200 Subject: [PATCH] blog: keep a scheduled post hidden on its own URL A future-dated post is left out of the prerender set, so its URL is served by the deployed Netlify function instead of a static file. Netlify passes only URL, SITE_NAME and SITE_ID to a function at runtime, so the process.env.CONTEXT read in isFuturePost was always undefined there, the check switched itself off, and the post rendered in full before its date. Take the production flag from runtimeConfig, which is captured at build time where CONTEXT is set, and move it under public so the same value is available wherever the check runs. Deploy previews and local dev are unaffected: their builds capture a non-production context, exactly as before. --- nuxt/composables/useBlogList.ts | 9 ++++++--- nuxt/nuxt.config.ts | 11 +++++++---- nuxt/server/plugins/analytics.ts | 2 +- nuxt/server/routes/blog/index.xml.ts | 9 ++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/nuxt/composables/useBlogList.ts b/nuxt/composables/useBlogList.ts index fa213fccfe..134b396d22 100644 --- a/nuxt/composables/useBlogList.ts +++ b/nuxt/composables/useBlogList.ts @@ -6,10 +6,13 @@ export const BLOG_PAGE_SIZE = 19 // which of these show as nav buttons; 'tips' has a route but isn't in that button list). export const BLOG_TAGS = ['how-to', 'node-red', 'ai', 'uns', 'dashboard', 'flowfuse', 'releases', 'news', 'plc', 'mqtt', 'opcua', 'modbus', 'tips'] -// Mirrors .eleventy.js's DEV_MODE_POSTS: future-dated posts are hidden outside production -// (Netlify sets CONTEXT), so deploy previews and dev can still preview scheduled posts. +// Mirrors .eleventy.js's DEV_MODE_POSTS: future-dated posts are hidden outside production, +// so deploy previews and dev can still preview scheduled posts. The flag is baked at build +// time (see nuxt.config.ts) rather than read from process.env here: a scheduled post gets no +// prerendered file, so its URL is served by the Netlify function, where process.env.CONTEXT +// is always undefined. That switched this check off and served the post before its date. export function isFuturePost(date: string | Date): boolean { - return new Date(date) > new Date() && process.env.CONTEXT === 'production' + return new Date(date) > new Date() && useRuntimeConfig().public.isProductionContext } export function useBlogList(tag: string | null, pageNumber: number) { diff --git a/nuxt/nuxt.config.ts b/nuxt/nuxt.config.ts index 76ef3c2709..2a433d5493 100644 --- a/nuxt/nuxt.config.ts +++ b/nuxt/nuxt.config.ts @@ -134,11 +134,14 @@ export default defineNuxtConfig({ devtools: { enabled: true }, modules: ['@nuxt/ui', '@nuxt/content', '@nuxtjs/seo', 'nuxt-studio', '@nuxt/image', './modules/docs-source', 'nuxt-llms'], - // Captured at build time (Netlify sets CONTEXT during the build, not necessarily - // in the deployed Function's runtime), then baked into the server bundle via - // runtimeConfig so analytics.ts doesn't depend on a process.env read at request time. + // Captured at build time (Netlify sets CONTEXT during the build, but passes only URL, + // SITE_NAME and SITE_ID to the deployed Function at runtime), then baked in via + // runtimeConfig so nothing depends on a process.env read at request time. Under `public` + // so the blog's scheduled-post check reads the same value everywhere it runs. runtimeConfig: { - isProductionContext: process.env.CONTEXT === 'production' + public: { + isProductionContext: process.env.CONTEXT === 'production' + } }, css: ['~/assets/css/theme.css'], diff --git a/nuxt/server/plugins/analytics.ts b/nuxt/server/plugins/analytics.ts index 11fc884c02..6ada0d465b 100644 --- a/nuxt/server/plugins/analytics.ts +++ b/nuxt/server/plugins/analytics.ts @@ -2,7 +2,7 @@ let headHtml: string | null = null let bodyHtml: string | null = null export default defineNitroPlugin((nitroApp) => { - if (import.meta.dev || !useRuntimeConfig().isProductionContext) return + if (import.meta.dev || !useRuntimeConfig().public.isProductionContext) return nitroApp.hooks.hook('render:html', async (html) => { if (html.bodyAppend.some(s => s.includes('cc.min.js'))) return diff --git a/nuxt/server/routes/blog/index.xml.ts b/nuxt/server/routes/blog/index.xml.ts index 2458a16ffc..5c33c3a34b 100644 --- a/nuxt/server/routes/blog/index.xml.ts +++ b/nuxt/server/routes/blog/index.xml.ts @@ -69,8 +69,10 @@ function escapeXml(value: string): string { .replace(/>/g, '>') } -function isFuturePost(date: string | Date): boolean { - return new Date(date) > new Date() && process.env.CONTEXT === 'production' +// See useBlogList.ts: the production flag is baked at build time, not read from +// process.env, which is empty of it inside the deployed Netlify function. +function isFuturePost(date: string | Date, isProductionContext: boolean): boolean { + return new Date(date) > new Date() && isProductionContext } // entry.body is a minimark tree: { value: MinimarkNode[] } where a node is @@ -178,7 +180,8 @@ export default defineEventHandler(async (event) => { const people = { ...teamPeople, ...guestPeople } // Full post bodies are heavy - cap the feed to the most recent posts rather // than shipping the entire multi-megabyte blog archive on every request. - const entries = allEntries.filter(entry => !isFuturePost(entry.date)).slice(0, 20) + const { isProductionContext } = useRuntimeConfig(event).public + const entries = allEntries.filter(entry => !isFuturePost(entry.date, isProductionContext)).slice(0, 20) // Mirrors useReleaseFeaturePage: splices plan-availability badges and changelog/docs // links into a release blog's body, resolved from its `features:` frontmatter.