@HappyDev's suggestion inspired this – while not particularly abstracted and could need some refactoring it does work and allows you to build Astro pages in Strapi using its dynamic zones and components by building corresponding Astro components:
/pages/index.astro
import SectionType1 from '../components/sections/SectionType1.astro'
// Import all the components you use to ensure styles and scripts are injected`
import renderPageContent from '../helpers/renderPageContent'
const page = await fetch(STRAPIENDPOINT) // <-- Strapi JSON
const contentParts = page.data.attributes.Sections
const pageContentString = await renderPageContent(contentParts)
---
<Layout>
<div set:html={pageContentString}></div>
</Layout>
/helpers/renderPageContent.js
export default async function (parts) {
const pagePartsContent = [];
parts.forEach(function (part) {
let componentRequest = {};
switch (part.__component) {
case "sections.SectionType1":
componentRequest.path = "SectionType1";
componentRequest.params = {
title: part.Title, // Your Strapi fields for this component
text: part.Text // watch out for capitalization
};
break;
// Add more cases for each component type and its fields
}
if (Object.keys(componentRequest).length) {
pagePartsContent.push(componentRequest);
}
});
let pagePartsContentString = "";
for (const componentRequest of pagePartsContent) {
let response = await fetch(
`${import.meta.env.SITE_URL}/components/${
componentRequest.path
}?data=${encodeURIComponent(JSON.stringify(componentRequest.params))}`
);
let contentString = await response.text();
// Strip out everything but the component markup so we avoid getting style and script tags in the body
contentString = contentString.match(/(<section.*?>.*<\/section>)/gims)[0];
pagePartsContentString += contentString;
}
return pagePartsContentString;
}
/components/sections/SectionType1.astro
---
export interface Props {
title: string;
text?: string;
}
const { title, text } = Astro.props as Props;
---
<section>
<h1>{ title }</h1>
<p>{ text }</p>
</section>
/pages/components/SectionType1.astro
---
import SectionType1 from '../../components/sections/SectionType1.astro';
import urlParser from '../../helpers/urlparser'
const { title, text } = urlParser(Astro.url);
---
<SectionType1
title={title}
text={text}
/>
/helpers/urlParser.js
export default function(url) {
return JSON.parse(Object.fromEntries(new URL(url).searchParams).data)
}