Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Metadata } from 'next';
import type { Metadata, ResolvingMetadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { headers } from 'next/headers';
import Link from 'next/link';
Expand All @@ -18,11 +18,28 @@ import { Header } from '@/components/header';
import { Footer } from '@/components/footer';
import { RotatingText } from '@/components/RotatingText';

export const metadata: Metadata = {
alternates: {
canonical: 'https://pairux.com',
},
};
// ad.network publisher claim for pairux.com. The meta tag lets ad.network
// verify ownership from the server-rendered HTML. The home page unit is a plain
// iframe to crawlproof's ad frame (not ad.js): the per-request CSP in
// middleware.ts has no frame-src for srcdoc frames to inherit, so it allows
// https://crawlproof.com in frame-src instead.
const AD_NETWORK_CODE = 'dYMbKXer6PUc3meh';
const HOME_AD_SLOT = 'f90b82f7-5e16-4419-b7b5-81723ca35c66';

// generateMetadata so the root layout's `other` tags (msapplication-*) are
// kept: a static `other` here would replace them rather than merge.
export async function generateMetadata(
_props: unknown,
parent: ResolvingMetadata
): Promise<Metadata> {
const { other } = await parent;
return {
alternates: {
canonical: 'https://pairux.com',
},
other: { ...other, 'ad.network': AD_NETWORK_CODE },
};
}

const softwareApplicationSchema = {
'@context': 'https://schema.org',
Expand Down Expand Up @@ -126,6 +143,24 @@ export default async function HomePage() {
<div className="bg-accent-100/50 absolute -bottom-40 -left-40 h-80 w-80 rounded-full blur-3xl" />
</section>

{/* ad.network home page unit */}
<aside
data-ad-network={AD_NETWORK_CODE}
data-cp-ad=""
data-slot={HOME_AD_SLOT}
data-format="banner_300x250"
className="flex justify-center bg-white pt-12"
>
<iframe
src={`https://crawlproof.com/api/ads/frame?slot=${HOME_AD_SLOT}&format=banner_300x250`}
width={300}
height={250}
loading="lazy"
style={{ border: 0 }}
title="Advertisement"
/>
</aside>

{/* Features Grid */}
<section className="bg-white py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ function buildCsp(nonce: string, embeddable: boolean): string {
"img-src 'self' data: blob: https:",
`media-src 'self' blob: ${mediaOrigin()}`,
"connect-src 'self' https: wss: https://crawlproof.com",
// The home page ad.network unit is an iframe to crawlproof's ad frame.
"frame-src 'self' https://crawlproof.com",
// /embed/* is the public player — any site may frame it. That is the whole
// point of the surface, and it is safe because the page is read-only: it
// renders a <video> plus outbound links, and performs no authenticated
Expand Down
Loading