From c7a16aed8706d009e25527b757bfc20f642729a7 Mon Sep 17 00:00:00 2001 From: Imogen Hardy Date: Fri, 12 Dec 2025 11:35:44 +0000 Subject: [PATCH 1/6] handler setup --- .../src/server/handler.hostedContent.web.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 dotcom-rendering/src/server/handler.hostedContent.web.ts diff --git a/dotcom-rendering/src/server/handler.hostedContent.web.ts b/dotcom-rendering/src/server/handler.hostedContent.web.ts new file mode 100644 index 00000000000..a2475e7880f --- /dev/null +++ b/dotcom-rendering/src/server/handler.hostedContent.web.ts @@ -0,0 +1,16 @@ +import { RequestHandler } from 'express'; +import { recordTypeAndPlatform } from './lib/logging-store'; +import { validateAsFEHostedContent } from '../model/validate'; +import { enhanceHostedContentType } from '../types/hostedContent'; + +export const handleArticle: RequestHandler = ({ body }, res) => { + recordTypeAndPlatform('article', 'web'); + + const frontendData = validateAsFEHostedContent(body); + const article = enhanceHostedContentType(frontendData); + // const { html, prefetchScripts } = renderHtml({ + // article, + // }); + + // res.status(200).set('Link', makePrefetchHeader(prefetchScripts)).send(html); +}; From 68771e7091dc1d530998ed6f12503cbc757ff723 Mon Sep 17 00:00:00 2001 From: Imogen Hardy Date: Fri, 12 Dec 2025 11:42:09 +0000 Subject: [PATCH 2/6] render file --- dotcom-rendering/src/server/handler.hostedContent.web.ts | 2 +- dotcom-rendering/src/server/render.hostedContent.web.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 dotcom-rendering/src/server/render.hostedContent.web.ts diff --git a/dotcom-rendering/src/server/handler.hostedContent.web.ts b/dotcom-rendering/src/server/handler.hostedContent.web.ts index a2475e7880f..51a9088d8e1 100644 --- a/dotcom-rendering/src/server/handler.hostedContent.web.ts +++ b/dotcom-rendering/src/server/handler.hostedContent.web.ts @@ -7,7 +7,7 @@ export const handleArticle: RequestHandler = ({ body }, res) => { recordTypeAndPlatform('article', 'web'); const frontendData = validateAsFEHostedContent(body); - const article = enhanceHostedContentType(frontendData); + const hostedContent = enhanceHostedContentType(frontendData); // const { html, prefetchScripts } = renderHtml({ // article, // }); diff --git a/dotcom-rendering/src/server/render.hostedContent.web.ts b/dotcom-rendering/src/server/render.hostedContent.web.ts new file mode 100644 index 00000000000..e69de29bb2d From 9f2f2fd2b0e835fa5a82c2b05047ef2a8189cb9d Mon Sep 17 00:00:00 2001 From: Imogen Hardy Date: Fri, 12 Dec 2025 12:05:39 +0000 Subject: [PATCH 3/6] extremely basic setup with no config data --- .../src/server/handler.hostedContent.web.ts | 10 +++-- .../src/server/render.hostedContent.web.ts | 0 .../src/server/render.hostedContent.web.tsx | 40 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) delete mode 100644 dotcom-rendering/src/server/render.hostedContent.web.ts create mode 100644 dotcom-rendering/src/server/render.hostedContent.web.tsx diff --git a/dotcom-rendering/src/server/handler.hostedContent.web.ts b/dotcom-rendering/src/server/handler.hostedContent.web.ts index 51a9088d8e1..894ed34c81a 100644 --- a/dotcom-rendering/src/server/handler.hostedContent.web.ts +++ b/dotcom-rendering/src/server/handler.hostedContent.web.ts @@ -2,15 +2,17 @@ import { RequestHandler } from 'express'; import { recordTypeAndPlatform } from './lib/logging-store'; import { validateAsFEHostedContent } from '../model/validate'; import { enhanceHostedContentType } from '../types/hostedContent'; +import { renderHtml } from './render.hostedContent.web'; +import { makePrefetchHeader } from './lib/header'; export const handleArticle: RequestHandler = ({ body }, res) => { recordTypeAndPlatform('article', 'web'); const frontendData = validateAsFEHostedContent(body); const hostedContent = enhanceHostedContentType(frontendData); - // const { html, prefetchScripts } = renderHtml({ - // article, - // }); + const { html, prefetchScripts } = renderHtml({ + hostedContent, + }); - // res.status(200).set('Link', makePrefetchHeader(prefetchScripts)).send(html); + res.status(200).set('Link', makePrefetchHeader(prefetchScripts)).send(html); }; diff --git a/dotcom-rendering/src/server/render.hostedContent.web.ts b/dotcom-rendering/src/server/render.hostedContent.web.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/dotcom-rendering/src/server/render.hostedContent.web.tsx b/dotcom-rendering/src/server/render.hostedContent.web.tsx new file mode 100644 index 00000000000..9596e3b7243 --- /dev/null +++ b/dotcom-rendering/src/server/render.hostedContent.web.tsx @@ -0,0 +1,40 @@ +import { HostedArticleLayout } from '../layouts/HostedArticleLayout'; +import { HostedGalleryLayout } from '../layouts/HostedGalleryLayout'; +import { renderToStringWithEmotion } from '../lib/emotion'; +import { HostedContent } from '../types/hostedContent'; +import { htmlPageTemplate } from './htmlPageTemplate'; + +type Props = { + hostedContent: HostedContent; +}; + +export const renderHtml = ({ hostedContent }: Props) => { + const { type, frontendData } = hostedContent; + + const title = `Advertiser content hosted by the Guardian: ${frontendData.title} | The Guardian`; + + const HostedLayout = + type === 'gallery' ? HostedGalleryLayout : HostedArticleLayout; + const renderingTarget = 'Web'; + + const { html, extractedCss } = renderToStringWithEmotion( + , + ); + + const pageHtml = htmlPageTemplate({ + scriptTags: [], + css: extractedCss, + html, + title, + description: frontendData.standfirst, + // @ts-expect-error no config data + guardian: {}, + canonicalUrl: '', + renderingTarget: 'Web', + // @ts-expect-error no config data + config: {}, + weAreHiring: false, + }); + + return { html: pageHtml, prefetchScripts: [] }; +}; From d3704a6834ffa33c417299451b262c91ccf782f2 Mon Sep 17 00:00:00 2001 From: Imogen Hardy Date: Fri, 12 Dec 2025 15:19:54 +0000 Subject: [PATCH 4/6] hook up dev and prod server --- dotcom-rendering/src/server/handler.hostedContent.web.ts | 8 ++++---- dotcom-rendering/src/server/render.hostedContent.web.tsx | 3 ++- dotcom-rendering/src/server/server.dev.ts | 4 ++++ dotcom-rendering/src/server/server.prod.ts | 2 ++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dotcom-rendering/src/server/handler.hostedContent.web.ts b/dotcom-rendering/src/server/handler.hostedContent.web.ts index 894ed34c81a..324d772242a 100644 --- a/dotcom-rendering/src/server/handler.hostedContent.web.ts +++ b/dotcom-rendering/src/server/handler.hostedContent.web.ts @@ -1,11 +1,11 @@ -import { RequestHandler } from 'express'; -import { recordTypeAndPlatform } from './lib/logging-store'; +import type { RequestHandler } from 'express'; import { validateAsFEHostedContent } from '../model/validate'; import { enhanceHostedContentType } from '../types/hostedContent'; -import { renderHtml } from './render.hostedContent.web'; import { makePrefetchHeader } from './lib/header'; +import { recordTypeAndPlatform } from './lib/logging-store'; +import { renderHtml } from './render.hostedContent.web'; -export const handleArticle: RequestHandler = ({ body }, res) => { +export const handleHostedContent: RequestHandler = ({ body }, res) => { recordTypeAndPlatform('article', 'web'); const frontendData = validateAsFEHostedContent(body); diff --git a/dotcom-rendering/src/server/render.hostedContent.web.tsx b/dotcom-rendering/src/server/render.hostedContent.web.tsx index 9596e3b7243..fda2858f73a 100644 --- a/dotcom-rendering/src/server/render.hostedContent.web.tsx +++ b/dotcom-rendering/src/server/render.hostedContent.web.tsx @@ -1,7 +1,7 @@ import { HostedArticleLayout } from '../layouts/HostedArticleLayout'; import { HostedGalleryLayout } from '../layouts/HostedGalleryLayout'; import { renderToStringWithEmotion } from '../lib/emotion'; -import { HostedContent } from '../types/hostedContent'; +import type { HostedContent } from '../types/hostedContent'; import { htmlPageTemplate } from './htmlPageTemplate'; type Props = { @@ -21,6 +21,7 @@ export const renderHtml = ({ hostedContent }: Props) => { , ); + // We currently don't send any of the data required for page config or window.guardian setup from frontend const pageHtml = htmlPageTemplate({ scriptTags: [], css: extractedCss, diff --git a/dotcom-rendering/src/server/server.dev.ts b/dotcom-rendering/src/server/server.dev.ts index 1754d362a3a..13fd2d9ec4d 100644 --- a/dotcom-rendering/src/server/server.dev.ts +++ b/dotcom-rendering/src/server/server.dev.ts @@ -15,6 +15,7 @@ import { import { handleAppsAssets } from './handler.assets.apps'; import { handleEditionsCrossword } from './handler.editionsCrossword'; import { handleFront, handleTagPage } from './handler.front.web'; +import { handleHostedContent } from './handler.hostedContent.web'; import { handleCricketMatchPage, handleFootballMatchListPage, @@ -106,6 +107,8 @@ renderer.get('/FootballMatchListPage/*url', handleFootballMatchListPage); renderer.get('/FootballTablesPage/*url', handleFootballTablesPage); renderer.get('/CricketMatchPage/*url', handleCricketMatchPage); renderer.get('/FootballMatchSummaryPage/*url', handleFootballMatchPage); +renderer.get('/HostedContent/*url', handleHostedContent); + // POST routes for running frontend locally renderer.post('/Article', handleArticle); renderer.post('/Interactive', handleInteractive); @@ -121,6 +124,7 @@ renderer.post('/FootballMatchListPage', handleFootballMatchListPage); renderer.post('/FootballTablesPage', handleFootballTablesPage); renderer.post('/CricketMatchPage', handleCricketMatchPage); renderer.post('/FootballMatchSummaryPage', handleFootballMatchPage); +renderer.post('/HostedContent', handleHostedContent); renderer.get('/assets/rendered-items-assets', handleAppsAssets); diff --git a/dotcom-rendering/src/server/server.prod.ts b/dotcom-rendering/src/server/server.prod.ts index 821be7d9bc6..d43ed67bfa1 100644 --- a/dotcom-rendering/src/server/server.prod.ts +++ b/dotcom-rendering/src/server/server.prod.ts @@ -17,6 +17,7 @@ import { import { handleAppsAssets } from './handler.assets.apps'; import { handleEditionsCrossword } from './handler.editionsCrossword'; import { handleFront, handleTagPage } from './handler.front.web'; +import { handleHostedContent } from './handler.hostedContent.web'; import { handleCricketMatchPage, handleFootballMatchListPage, @@ -75,6 +76,7 @@ export const prodServer = (): void => { logRenderTime, handleFootballMatchPage, ); + app.post('/HostedContent', logRenderTime, handleHostedContent); app.post( '/EmailNewsletters', From 12a29f7a091f560bd5c41ca7a72a16c32a29bd50 Mon Sep 17 00:00:00 2001 From: Imogen Hardy Date: Fri, 12 Dec 2025 15:37:26 +0000 Subject: [PATCH 5/6] add links to dev landing page --- dotcom-rendering/src/server/dev-index.html | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dotcom-rendering/src/server/dev-index.html b/dotcom-rendering/src/server/dev-index.html index a085e5216b0..0c3d9de8cc6 100644 --- a/dotcom-rendering/src/server/dev-index.html +++ b/dotcom-rendering/src/server/dev-index.html @@ -97,6 +97,24 @@

Pages

>🏏 Cricket Match Page +
  • + 💷 Hosted Article +
  • +
  • + 💰 Hosted Video +
  • +
  • + 💸 Hosted Gallery +
  • Date: Fri, 12 Dec 2025 15:47:27 +0000 Subject: [PATCH 6/6] add the real prefetch scripts we can currently use --- .../src/server/render.hostedContent.web.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dotcom-rendering/src/server/render.hostedContent.web.tsx b/dotcom-rendering/src/server/render.hostedContent.web.tsx index fda2858f73a..81f76414bad 100644 --- a/dotcom-rendering/src/server/render.hostedContent.web.tsx +++ b/dotcom-rendering/src/server/render.hostedContent.web.tsx @@ -1,6 +1,9 @@ +import { isString } from '@guardian/libs'; import { HostedArticleLayout } from '../layouts/HostedArticleLayout'; import { HostedGalleryLayout } from '../layouts/HostedGalleryLayout'; +import { getModulesBuild, getPathFromManifest } from '../lib/assets'; import { renderToStringWithEmotion } from '../lib/emotion'; +import { polyfillIO } from '../lib/polyfill.io'; import type { HostedContent } from '../types/hostedContent'; import { htmlPageTemplate } from './htmlPageTemplate'; @@ -21,6 +24,18 @@ export const renderHtml = ({ hostedContent }: Props) => { , ); + // We don't send A/B tests or switches from frontend yet- do we need to? + const build = getModulesBuild({ + tests: {}, + switches: {}, + }); + + const prefetchScripts = [ + polyfillIO, + getPathFromManifest(build, 'frameworks.js'), + getPathFromManifest(build, 'index.js'), + ].filter(isString); + // We currently don't send any of the data required for page config or window.guardian setup from frontend const pageHtml = htmlPageTemplate({ scriptTags: [], @@ -37,5 +52,5 @@ export const renderHtml = ({ hostedContent }: Props) => { weAreHiring: false, }); - return { html: pageHtml, prefetchScripts: [] }; + return { html: pageHtml, prefetchScripts }; };