diff --git a/gatsby-ssr.tsx b/gatsby-ssr.tsx
index 8da52b1e28..c34c350f36 100644
--- a/gatsby-ssr.tsx
+++ b/gatsby-ssr.tsx
@@ -1,7 +1,8 @@
import React from 'react';
+import type { GatsbySSR } from 'gatsby';
import { getSandpackCssText } from '@codesandbox/sandpack-react';
-const onRenderBody = ({ setHeadComponents }: { setHeadComponents: (components: React.ReactNode[]) => void }) => {
+const onRenderBody: GatsbySSR['onRenderBody'] = ({ setHeadComponents }) => {
const inlineScripts: React.ReactNode[] = [];
// OneTrust consent management, inspiration taken from gatsby-google-tagmanager implementation
@@ -45,10 +46,80 @@ const onRenderBody = ({ setHeadComponents }: { setHeadComponents: (components: R
setHeadComponents(inlineScripts);
};
+type StyleComponent = React.ReactElement<
+ {
+ 'data-href'?: string;
+ href?: string;
+ },
+ 'style'
+>;
+
+const isStyleComponent = (node: React.ReactNode): node is StyleComponent =>
+ React.isValidElement(node) && node.type === 'style';
+
+const getStyleHref = (node: StyleComponent): string | undefined => node.props?.['data-href'] ?? node.props?.href;
+
+// Only Gatsby-emitted stylesheet chunks have a data-href/href; inline styles
+// like Sandpack's do not, and must not be reordered or replaced.
+const isExtractableStyleNode = (node: React.ReactNode): node is StyleComponent =>
+ isStyleComponent(node) && getStyleHref(node) !== undefined;
+
+const isGlobalStyleNode = (node: React.ReactNode): boolean => {
+ if (!isExtractableStyleNode(node)) {
+ return false;
+ }
+ // Heroku review apps set assetPrefix, which causes Gatsby to emit absolute
+ // URLs. Normalize to a pathname so the regex matches both forms.
+ try {
+ const stylePathname = new URL(getStyleHref(node) ?? '', 'http://localhost').pathname;
+ return /^\/styles\.[a-zA-Z0-9]+\.css$/.test(stylePathname);
+ } catch {
+ return false;
+ }
+};
+
+/**
+ * Gatsby inlines all styles from the app inside a `` tag. This makes
+ * rendered HTML hostile to LLM/AI crawlers, which often bail before reaching
+ * any content because of the wall of CSS at the top of the document.
+ * Replacing each `` with a ``
+ * pointing at the same already-emitted CSS file keeps styling intact while
+ * leaving the document body close to the top of the head.
+ *
+ * The same workaround is described in https://github.com/gatsbyjs/gatsby/issues/1526.
+ *
+ * Global styles sort first within the set of Gatsby-emitted stylesheet chunks
+ * to preserve cascade order; non-style head components and any inline `