The behavior you're experiencing—where metatags are rendered inside the <body> instead of the <head>—is a known and intentional result of how Next.js handles streaming metadata when using async data fetching in generateMetadata.
When generateMetadata is asynchronous (e.g., it performs a fetch to Contentful), Next.js may start streaming the page's HTML before the metadata is ready. In this case, the metadata is injected into the body as the page streams, rather than blocking the initial render to wait for the metadata. This is done to improve perceived performance for users. However, if generateMetadata resolves synchronously (such as when you return a literal object), the metadata is placed in the <head> as expected.
This behavior is especially triggered when you use Suspense, loading.tsx, or template.tsx, as these components can cause the UI to stream before metadata is available. The issue is more pronounced in recent Next.js versions (15.2 and above), and is documented and discussed in several issues: