Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PCC-1128] Improve OpenGraph information generation #319

Merged
merged 2 commits into from
Oct 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ export default async function ArticlesListTemplate() {
export function generateMetadata() {
return {
title: "Decoupled Next PCC Demo",
description: "Generated by create-pantheon-decoupled-kit.",
description: "Articles",
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ export default async function ArticlePage({ params }: ArticlePageProps) {
return notFound();
}

const recommendedArticles =
await PCCConvenienceFunctions.getRecommendedArticles(article.id);

return (
<Layout>
<div className="prose mx-4 mt-16 text-black sm:mx-6 md:mx-auto">
Expand Down
2 changes: 1 addition & 1 deletion starters/nextjs-starter-approuter-ts/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ export default async function Home() {
export function generateMetadata() {
return {
title: "Decoupled Next PCC Demo",
description: "Generated by create-pantheon-decoupled-kit.",
description: "Homepage",
};
}
49 changes: 25 additions & 24 deletions starters/nextjs-starter-approuter-ts/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArticleWithoutContent } from "@pantheon-systems/pcc-react-sdk";
import { clsx, type ClassValue } from "clsx";
import { Metadata } from "next";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
Expand All @@ -15,46 +16,46 @@ export function formatDate(input: string | number): string {
});
}

interface DateInputObject {
msSinceEpoch: string;
}

function isDateInputObject(v: DateInputObject | unknown): v is DateInputObject {
return (v as DateInputObject).msSinceEpoch != null;
}

export function getSeoMetadata(article: ArticleWithoutContent) {
const tags = article.tags && article.tags.length > 0 ? article.tags : [];
let authors = [];
let publishedTime = null;

// Collecting data from metadata fields
Object.entries(article.metadata || {}).forEach(([key, val]) => {
if (key.toLowerCase().trim() === "author" && val) authors = [val];
else if (key.toLowerCase().trim() === "date" && isDateInputObject(val))
publishedTime = new Date(val.msSinceEpoch).toISOString();
});

export function getSeoMetadata(article: ArticleWithoutContent): Metadata {
const tags: string[] =
article.tags && article.tags.length > 0 ? article.tags : [];
const imageProperties = [
article.metadata?.image,
article.metadata?.["Hero Image"],
// Extend as needed
]
.filter((url): url is string => typeof url === "string")
.map((url) => ({ url }));
const description = article.metadata?.description
? String(article.metadata?.description)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have enough context yet to review the PR. But on a casual glance, I noticed this.
optional chaining again in String(article.metadata?.description) might not necessary. I see this in other places too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah that's true, I'll pay more attention to these. It'd be good to catch these in linting too, I'll see if I can get a PR out adding an ESLint rule for this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would be nice .. 👍

: "Article hosted using Pantheon Content Cloud";

let authors: Metadata["authors"] = [];

const description = "Article hosted using Pantheon Content Publisher";
// Collecting data from metadata fields
Object.entries(article.metadata || {}).forEach(([k, v]) => {
const key = k.toLowerCase().trim();

switch (key) {
case "author": {
if (typeof v === "string") {
authors = [{ name: v }];
}
break;
}
}
});

return {
title: article.title,
description,
tags,
keywords: tags,
authors,
publishedTime,
openGraph: {
type: "website",
title: article.title,
description,
images: imageProperties,
description,
},
};
}
61 changes: 45 additions & 16 deletions starters/nextjs-starter-ts/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArticleWithoutContent } from "@pantheon-systems/pcc-react-sdk";
import { clsx, type ClassValue } from "clsx";
import { OpenGraph } from "next-seo/lib/types";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
Expand All @@ -24,30 +25,58 @@ function isDateInputObject(v: DateInputObject | unknown): v is DateInputObject {
}

export function getSeoMetadata(article: ArticleWithoutContent) {
const tags = article.tags && article.tags.length > 0 ? article.tags : [];
let authors = [];
let publishedTime = null;

// Collecting data from metadata fields
Object.entries(article.metadata || {}).forEach(([key, val]) => {
if (key.toLowerCase().trim() === "author" && val) authors = [val];
else if (key.toLowerCase().trim() === "date" && isDateInputObject(val))
publishedTime = new Date(val.msSinceEpoch).toISOString();
});

const tags: string[] =
article.tags && article.tags.length > 0 ? article.tags : [];
const imageProperties = [
article.metadata?.image,
article.metadata?.["Hero Image"],
// Extend as needed
]
.filter((url): url is string => typeof url === "string")
.map((url) => ({ url }));
const description = article.metadata?.description
? String(article.metadata?.description)
: "Article hosted using Pantheon Content Cloud";

let authors: string[] = [];
let publishedTime: number | null = article.publishedDate;

// Collecting data from metadata fields
Object.entries(article.metadata || {}).forEach(([k, v]) => {
const key = k.toLowerCase().trim();

switch (key) {
case "author": {
if (typeof v === "string") {
authors = [v];
}
break;
}
case "date": {
if (isDateInputObject(v)) {
// Prefer the date from the metadata, if it exists
publishedTime = parseInt(v.msSinceEpoch);
}
break;
}
}
});

return {
title: article.title,
description: "Article hosted using Pantheon Content Cloud",
tags,
authors,
publishedTime,
images: imageProperties,
description,
openGraph: {
type: "website",
title: article.title,
images: imageProperties,
description,
article: {
authors: authors,
tags: tags,
...(publishedTime && {
publishedTime: new Date(publishedTime).toISOString(),
}),
},
} satisfies OpenGraph,
};
}
14 changes: 1 addition & 13 deletions starters/nextjs-starter-ts/pages/articles/[...uri].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,7 @@ export default function ArticlePage({ article, grant }: ArticlePageProps) {
<NextSeo
title={seoMetadata.title}
description={seoMetadata.description}
openGraph={{
type: "website",
title: seoMetadata.title,
description: seoMetadata.description,
images: seoMetadata.images,
article: {
authors: seoMetadata.authors,
tags: seoMetadata.tags,
...(seoMetadata.publishedTime && {
publishedTime: seoMetadata.publishedTime,
}),
},
}}
openGraph={seoMetadata.openGraph}
/>

<div className="prose mx-4 mt-16 text-black sm:mx-6 md:mx-auto">
Expand Down
24 changes: 2 additions & 22 deletions starters/nextjs-starter-ts/pages/examples/ssg-isr/[uri].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,22 @@ import {
import { GetStaticPaths, GetStaticProps } from "next";
import { NextSeo } from "next-seo";
import { StaticArticleView } from "../../../components/article-view";
import { ArticleGrid } from "../../../components/grid";
import Layout from "../../../components/layout";
import { getSeoMetadata } from "../../../lib/utils";

interface ArticlePageProps {
article: Article;
recommendedArticles: Article[];
}

export default function ArticlePage({
article,
recommendedArticles,
}: ArticlePageProps) {
export default function ArticlePage({ article }: ArticlePageProps) {
const seoMetadata = getSeoMetadata(article);

return (
<Layout>
<NextSeo
title={seoMetadata.title}
description={seoMetadata.description}
openGraph={{
type: "website",
title: seoMetadata.title,
description: seoMetadata.description,
article: {
authors: seoMetadata.authors,
tags: seoMetadata.tags,
...(seoMetadata.publishedTime && {
publishedTime: seoMetadata.publishedTime,
}),
},
}}
openGraph={seoMetadata.openGraph}
/>

<div className="prose mx-4 mt-16 text-black sm:mx-6 md:mx-auto">
Expand Down Expand Up @@ -64,13 +48,9 @@ export const getStaticProps: GetStaticProps<{}, { uri: string }> = async ({
};
}

const recommendedArticles =
await PCCConvenienceFunctions.getRecommendedArticles(article.id);

return {
props: {
article,
recommendedArticles,
},
};
} catch (e) {
Expand Down
57 changes: 42 additions & 15 deletions starters/nextjs-starter/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,56 @@ function isDateInputObject(v) {

export function getSeoMetadata(article) {
const tags = article.tags && article.tags.length > 0 ? article.tags : [];
let authors = [];
let publishedTime = null;

// Collecting data from metadata fields
Object.entries(article.metadata || {}).forEach(([key, val]) => {
if (key.toLowerCase().trim() === "author" && val) authors = [val];
else if (key.toLowerCase().trim() === "date" && isDateInputObject(val))
publishedTime = new Date(val.msSinceEpoch).toISOString();
});

const imageProperties = [
article.metadata?.image,
article.metadata?.["Hero Image"],
// Extend as needed
]
.filter((url) => typeof url === "string")
.map((url) => ({ url }));
const description = article.metadata?.description
? String(article.metadata?.description)
: "Article hosted using Pantheon Content Cloud";

let authors = [];
let publishedTime = article.publishedDate;

// Collecting data from metadata fields
Object.entries(article.metadata || {}).forEach(([k, v]) => {
const key = k.toLowerCase().trim();

switch (key) {
case "author": {
if (typeof v === "string") {
authors = [v];
}
break;
}
case "date": {
if (isDateInputObject(v)) {
// Prefer the date from the metadata, if it exists
publishedTime = parseInt(v.msSinceEpoch);
}
break;
}
}
});

return {
title: article.title,
description: "Article hosted using Pantheon Content Cloud",
tags,
authors,
publishedTime,
images: imageProperties,
description,
openGraph: {
type: "website",
title: article.title,
images: imageProperties,
description,
article: {
authors: authors,
tags: tags,
...(publishedTime && {
publishedTime: new Date(publishedTime).toISOString(),
}),
},
},
};
}
14 changes: 1 addition & 13 deletions starters/nextjs-starter/pages/articles/[...uri].jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,7 @@ export default function ArticlePage({ article, grant }) {
<NextSeo
title={seoMetadata.title}
description={seoMetadata.description}
openGraph={{
type: "website",
title: seoMetadata.title,
description: seoMetadata.description,
images: seoMetadata.images,
article: {
authors: seoMetadata.authors,
tags: seoMetadata.tags,
...(seoMetadata.publishedTime && {
publishedTime: seoMetadata.publishedTime,
}),
},
}}
openGraph={seoMetadata.openGraph}
/>

<div className="prose mx-4 mt-16 text-black sm:mx-6 md:mx-auto">
Expand Down
14 changes: 1 addition & 13 deletions starters/nextjs-starter/pages/examples/ssg-isr/[uri].jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { PCCConvenienceFunctions } from "@pantheon-systems/pcc-react-sdk";
import { NextSeo } from "next-seo";
import { StaticArticleView } from "../../../components/article-view";
import { ArticleGrid } from "../../../components/grid";
import Layout from "../../../components/layout";
import { getSeoMetadata } from "../../../lib/utils";

Expand All @@ -13,18 +12,7 @@ export default function ArticlePage({ article, recommendedArticles }) {
<NextSeo
title={seoMetadata.title}
description={seoMetadata.description}
openGraph={{
type: "website",
title: seoMetadata.title,
description: seoMetadata.description,
article: {
authors: seoMetadata.authors,
tags: seoMetadata.tags,
...(seoMetadata.publishedTime && {
publishedTime: seoMetadata.publishedTime,
}),
},
}}
openGraph={seoMetadata.openGraph}
/>

<div className="prose mx-4 mt-16 text-black sm:mx-6 md:mx-auto">
Expand Down
Loading