inside ,
+ * which is invalid HTML. Browsers move
elements from to during
+ * parsing, causing a server/client mismatch → React hydration error #418.
+ *
+ * This version injects scripts client-side via useEffect, avoiding the mismatch entirely.
*/
export function HeadInjection({ siteSettings }: { siteSettings: SiteSettings }) {
const headerCode = siteSettings?.headerCodeInjection?.enabled
? (siteSettings.headerCodeInjection.code ?? '')
: ''
- if (!headerCode) return null
- return
+ useEffect(() => {
+ if (!headerCode) return
+ injectCodeSnippet(headerCode, 'head', 'fd-head-injection')
+ }, [headerCode])
+
+ return null
}
/**
- * FooterInjection — same pattern as HeadInjection.
+ * FooterInjection — injects footer code safely via useEffect.
*/
export function FooterInjection({ siteSettings }: { siteSettings: SiteSettings }) {
const footerCode = siteSettings?.footerCodeInjection?.enabled
? (siteSettings.footerCodeInjection.code ?? '')
: ''
- if (!footerCode) return null
- return
+ useEffect(() => {
+ if (!footerCode) return
+ injectCodeSnippet(footerCode, 'body', 'fd-footer-injection')
+ }, [footerCode])
+
+ return null
+}
+
+/**
+ * Parse an HTML string and inject its elements (script, link, meta, style, etc.)
+ * into the specified target. Scripts are re-created so the browser executes them.
+ */
+function injectCodeSnippet(
+ html: string,
+ target: 'head' | 'body',
+ markerId: string,
+) {
+ // Avoid double-injection on re-renders
+ if (document.getElementById(markerId)) return
+
+ const container = document.createElement('div')
+ container.id = markerId
+ container.style.display = 'none'
+
+ // Parse the HTML string
+ const parser = new DOMParser()
+ const doc = parser.parseFromString(html, 'text/html')
+
+ // Collect all elements from both and of the parsed fragment
+ const elements = [
+ ...Array.from(doc.head.children),
+ ...Array.from(doc.body.children),
+ ]
+
+ const targetEl = target === 'head' ? document.head : document.body
+
+ for (const el of elements) {
+ if (el.tagName === 'SCRIPT') {
+ // Scripts must be re-created to execute
+ const script = document.createElement('script')
+ Array.from(el.attributes).forEach((attr) => {
+ script.setAttribute(attr.name, attr.value)
+ })
+ if (el.textContent) script.textContent = el.textContent
+ script.dataset.injectedBy = markerId
+ targetEl.appendChild(script)
+ } else {
+ // link, meta, style, noscript — clone directly
+ const clone = el.cloneNode(true) as HTMLElement
+ clone.dataset.injectedBy = markerId
+ targetEl.appendChild(clone)
+ }
+ }
+
+ // Append invisible marker so we can detect double-injection
+ targetEl.appendChild(container)
}
diff --git a/src/components/Logo/Logo.tsx b/src/components/Logo/Logo.tsx
index 0bf06dc..09799bc 100644
--- a/src/components/Logo/Logo.tsx
+++ b/src/components/Logo/Logo.tsx
@@ -31,6 +31,8 @@ export const Logo = (props: Props) => {
className={clsx(className)}
style={{ width: '150px', height: 'auto' }}
src={src}
+ width={150}
+ height={88}
/>
)
-}
\ No newline at end of file
+}
diff --git a/src/globals/PopupAnnouncement/Component.tsx b/src/globals/PopupAnnouncement/Component.tsx
index d7d0510..17cb9f2 100644
--- a/src/globals/PopupAnnouncement/Component.tsx
+++ b/src/globals/PopupAnnouncement/Component.tsx
@@ -1,6 +1,7 @@
'use client'
import React, { useState, useEffect } from 'react'
+import Image from 'next/image'
import type { Media, Page } from '@/payload-types'
type LinkGroup = {
@@ -104,6 +105,12 @@ export const PopupAnnouncementComponent: React.FC
= ({
const href = resolveUrl(ctaLink)
const newTab = ctaLink?.newTab ?? false
+ // ── FIX: Resolve optimized image URL via Payload size or fall back to original ──
+ const imageSrc = media?.sizes?.medium?.url || media?.sizes?.large?.url || media?.url || ''
+ const imageWidth = media?.sizes?.medium?.width || media?.sizes?.large?.width || media?.width || 560
+ const imageHeight = media?.sizes?.medium?.height || media?.sizes?.large?.height || media?.height || 720
+ const isSvg = media?.mimeType === 'image/svg+xml'
+
return (
<>
{/* Backdrop */}
@@ -167,14 +174,29 @@ export const PopupAnnouncementComponent: React.FC = ({
)}
- {/* Optional image */}
- {media?.url && (
+ {/* Optional image — FIX: use next/image instead of raw