Skip to content

Instantly share code, notes, and snippets.

@rshackleton

rshackleton/index.tsx

Last active Mar 27, 2021
Embed
What would you like to do?
Next.js page with partial content
import { GetStaticProps } from 'next';
import { useRouter } from 'next/router';
import Head from 'next/head';
import React, { useEffect, useState } from 'react';
import { getGatedContentItem } from '../lib/getGatedContentItem';
export type ContentItemModel = {
date: string;
freeContent: string;
gatedContent: string;
title: string;
};
export type IndexProps = {
contentItem: ContentItemModel;
preview: boolean;
};
const Index: React.FC<IndexProps> = ({ contentItem, preview }) => {
const router = useRouter();
// We need to store the gated content in state so we can update it later.
const [gatedContent, setGatedContent] = useState(contentItem.gatedContent);
// We store the chosen animal in state - this can be whatever data needed for your gate e.g. email sign up.
const [animal, setAnimal] = useState('');
// Track the state of the API request.
const [loading, setLoading] = useState(false);
useEffect(() => {
doAsync();
async function doAsync() {
if (router.isFallback) {
return;
}
if (!contentItem) {
return;
}
// If we think this is a Google request then fetch content immediately.
const isGoogle = navigator.userAgent.toLowerCase().includes('googlebot');
if (isGoogle) {
setLoading(true);
const content = await fetchContent({ animal });
setGatedContent(content);
setLoading(false);
}
}
}, []);
if (router.isFallback) {
return <div>Loading...</div>;
}
return (
<div>
<Head>
<title>{contentItem.title}</title>
<link rel="icon" href="/favicon.ico" />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(getStructuredData(contentItem), null, 2) }}
/>
<meta name="google-site-verification" content="jQ-3SoNf7fN4tNyYecVHivFtUVcRGa7OMBL1BXsLXt0" />
</Head>
<main className="container py-8 px-4 mx-auto">
<div className="prose mx-auto lg:prose-xl">
<h1>{contentItem.title}</h1>
{loading && <p>We are loading your content! 👀</p>}
{!loading && (preview || !gatedContent) && (
<div className="no-paywall">
<div className="mb-6" dangerouslySetInnerHTML={{ __html: contentItem.freeContent }} />
<form
className="grid grid-cols-2 gap-4 mb-6"
onSubmit={async (event) => {
event.preventDefault();
const content = await fetchContent({
animal: animal,
});
setGatedContent(content);
}}
>
<label className="font-bold" htmlFor="favourite-animal">
What is your favourite animal?
</label>
<input
id="favourite-animal"
className="rounded"
name="favourite-animal"
type="text"
placeholder="🐶"
value={animal}
onChange={(event) => setAnimal(event.target.value)}
/>
<button className="col-start-2 px-6 py-2 rounded place-self-end text-white bg-purple-700" type="submit">
Gimme My Content!
</button>
</form>
</div>
)}
{!loading && (preview || gatedContent) && (
<div className="paywall" dangerouslySetInnerHTML={{ __html: gatedContent }} />
)}
<small>Last Modified: {formatDate(contentItem.date)}</small>
</div>
</main>
</div>
);
/** Call the serverless function to retrieve gated content. */
async function fetchContent(formData: { animal?: string }): Promise<string | null> {
const res = await fetch('/api/gated-content', {
body: JSON.stringify(formData),
cache: 'no-cache',
credentials: 'omit',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
const data = await res.json();
if (res.ok) {
return data.gatedContent as string | null;
} else {
console.error(`Error: ${data.message}`);
return null;
}
}
};
export default Index;
export const getStaticProps: GetStaticProps<IndexProps> = async ({ preview = false }) => {
const contentItem = await getGatedContentItem(preview);
if (!contentItem) {
console.log(`Could not find content item.`);
return {
notFound: true,
};
}
return {
props: {
contentItem: {
date: contentItem.system.lastModified.toISOString(),
freeContent: contentItem.free_content.value,
// If we're in preview mode then show the full content.
gatedContent: preview ? contentItem.gated_content.value : '',
title: contentItem.title.value,
},
preview,
},
revalidate: true,
};
};
function formatDate(input: string): string {
const formatter = Intl.DateTimeFormat(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
return formatter.format(new Date(input));
}
function getStructuredData(item: ContentItemModel) {
const data: any = {
'@context': 'https://schema.org',
'@type': 'WebPage',
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://${process.env.NEXT_PUBLIC_HOST}/`,
},
headline: item.title,
datePublished: item.date,
dateModified: item.date,
author: {
'@type': 'Person',
name: 'Richard Shackleton',
},
// Important for Google to understand the content is gated.
isAccessibleForFree: 'False',
hasPart: {
'@type': 'WebPageElement',
isAccessibleForFree: 'False',
cssSelector: '.paywall',
},
};
return data;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment