aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-05-23 19:02:20 +0200
committerArmand Philippot <git@armandphilippot.com>2022-05-23 19:02:20 +0200
commit5ebd7c14f7303a0feb8ec1d902ecd0e287d929c3 (patch)
tree4e0e0352ac7598fa4e186230e93bcc4c1ac6b810 /src
parent34e216546151eaf8a0a3cbb0bc8b65dae4c63bf2 (diff)
refactor(schema): use helpers function to avoid repeat between pages
Diffstat (limited to 'src')
-rw-r--r--src/pages/article/[slug].tsx94
-rw-r--r--src/pages/blog/index.tsx49
-rw-r--r--src/pages/blog/page/[number].tsx49
-rw-r--r--src/pages/contact.tsx60
-rw-r--r--src/pages/cv.tsx61
-rw-r--r--src/pages/index.tsx26
-rw-r--r--src/pages/mentions-legales.tsx62
-rw-r--r--src/pages/projets/[slug].tsx60
-rw-r--r--src/pages/projets/index.tsx62
-rw-r--r--src/pages/recherche/index.tsx49
-rw-r--r--src/pages/sujet/[slug].tsx61
-rw-r--r--src/pages/thematique/[slug].tsx60
-rw-r--r--src/utils/helpers/schema-org.ts224
13 files changed, 468 insertions, 449 deletions
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 2878538..c72d48e 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -18,6 +18,12 @@ import {
type NextPageWithLayout,
} from '@ts/types/app';
import { loadTranslation, type Messages } from '@utils/helpers/i18n';
+import {
+ getBlogSchema,
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import usePrism, { type OptionalPrismPlugin } from '@utils/hooks/use-prism';
import useReadingTime from '@utils/hooks/use-reading-time';
@@ -29,7 +35,6 @@ import Script from 'next/script';
import { ParsedUrlQuery } from 'querystring';
import { HTMLAttributes } from 'react';
import { useIntl } from 'react-intl';
-import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts';
import useSWR from 'swr';
type ArticlePageProps = {
@@ -108,64 +113,35 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({
const { website } = useSettings();
const { asPath } = useRouter();
- const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- lastReviewed: dates.update,
- name: seo.title,
- description: seo.description,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${pageUrl}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const blogSchema: Blog = {
- '@id': `${website.url}/#blog`,
- '@type': 'Blog',
- blogPost: { '@id': `${website.url}/#article` },
- isPartOf: {
- '@id': `${pageUrl}`,
- },
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- };
-
- const blogPostSchema: BlogPosting = {
- '@id': `${website.url}/#article`,
- '@type': 'BlogPosting',
- name: title,
+ const webpageSchema = getWebPageSchema({
description: intro,
- articleBody: content,
- author: { '@id': `${website.url}/#branding` },
- commentCount: commentsCount,
- copyrightYear: pagePublicationDate.getFullYear(),
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- discussionUrl: `${pageUrl}/#comments`,
- editor: { '@id': `${website.url}/#branding` },
- headline: title,
- image: cover?.src,
- inLanguage: website.locales.default,
- isPartOf: {
- '@id': `${website.url}/blog`,
- },
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- thumbnailUrl: cover?.src,
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, blogSchema, blogPostSchema],
- };
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ updateDate: dates.update,
+ });
+ const blogSchema = getBlogSchema({
+ isSinglePage: true,
+ locale: website.locales.default,
+ slug: asPath,
+ });
+ const blogPostSchema = getSinglePageSchema({
+ commentsCount,
+ content,
+ cover: cover?.src,
+ dates,
+ description: intro,
+ id: 'article',
+ kind: 'post',
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const schemaJsonLd = getSchemaJson([
+ webpageSchema,
+ blogSchema,
+ blogPostSchema,
+ ]);
const prismPlugins: OptionalPrismPlugin[] = ['command-line', 'line-numbers'];
const { attributes, className } = usePrism({ plugins: prismPlugins });
@@ -202,6 +178,8 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({
prismClassNameReplacer
);
+ const pageUrl = `${website.url}${asPath}`;
+
return (
<>
<Head>
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index dd04fad..cfd6f8c 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -23,6 +23,11 @@ import {
getPageLinkFromRawData,
getPostsList,
} from '@utils/helpers/pages';
+import {
+ getBlogSchema,
+ getSchemaJson,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import usePagination from '@utils/hooks/use-pagination';
import useSettings from '@utils/hooks/use-settings';
@@ -31,7 +36,6 @@ import Head from 'next/head';
import { useRouter } from 'next/router';
import Script from 'next/script';
import { useIntl } from 'react-intl';
-import { Blog, Graph, WebPage } from 'schema-dts';
type BlogPageProps = {
articles: EdgesResponse<RawArticle>;
@@ -80,37 +84,18 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
},
{ websiteName: website.name }
);
- const pageUrl = `${website.url}${asPath}`;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: pageTitle,
+ const webpageSchema = getWebPageSchema({
description: pageDescription,
- inLanguage: website.locales.default,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${website.url}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const blogSchema: Blog = {
- '@id': `${website.url}/#blog`,
- '@type': 'Blog',
- author: { '@id': `${website.url}/#branding` },
- creator: { '@id': `${website.url}/#branding` },
- editor: { '@id': `${website.url}/#branding` },
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, blogSchema],
- };
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const blogSchema = getBlogSchema({
+ isSinglePage: false,
+ locale: website.locales.default,
+ slug: asPath,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]);
const {
data,
@@ -149,7 +134,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
<Head>
<title>{pageTitle}</title>
<meta name="description" content={pageDescription} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:description" content={pageDescription} />
diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx
index 8f50ddd..78b1db4 100644
--- a/src/pages/blog/page/[number].tsx
+++ b/src/pages/blog/page/[number].tsx
@@ -26,6 +26,11 @@ import {
getPageLinkFromRawData,
getPostsList,
} from '@utils/helpers/pages';
+import {
+ getBlogSchema,
+ getSchemaJson,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useRedirection from '@utils/hooks/use-redirection';
import useSettings from '@utils/hooks/use-settings';
@@ -35,7 +40,6 @@ import { useRouter } from 'next/router';
import Script from 'next/script';
import { ParsedUrlQuery } from 'querystring';
import { useIntl } from 'react-intl';
-import { Blog, Graph, WebPage } from 'schema-dts';
type BlogPageProps = {
articles: EdgesResponse<RawArticle>;
@@ -95,37 +99,18 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
},
{ websiteName: website.name }
);
- const pageUrl = `${website.url}${asPath}`;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: pageTitle,
+ const webpageSchema = getWebPageSchema({
description: pageDescription,
- inLanguage: website.locales.default,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${website.url}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const blogSchema: Blog = {
- '@id': `${website.url}/#blog`,
- '@type': 'Blog',
- author: { '@id': `${website.url}/#branding` },
- creator: { '@id': `${website.url}/#branding` },
- editor: { '@id': `${website.url}/#branding` },
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, blogSchema],
- };
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const blogSchema = getBlogSchema({
+ isSinglePage: false,
+ locale: website.locales.default,
+ slug: asPath,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]);
const thematicsListTitle = intl.formatMessage({
defaultMessage: 'Thematics',
@@ -144,7 +129,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
<Head>
<title>{pageTitle}</title>
<meta name="description" content={pageDescription} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="website" />
<meta property="og:title" content={pageTitleWithPageNumber} />
<meta property="og:description" content={pageDescription} />
diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx
index c0d6c79..2392fe2 100644
--- a/src/pages/contact.tsx
+++ b/src/pages/contact.tsx
@@ -6,10 +6,15 @@ import SocialMedia from '@components/organisms/widgets/social-media';
import { getLayout } from '@components/templates/layout/layout';
import PageLayout from '@components/templates/page/page-layout';
import { meta } from '@content/pages/contact.mdx';
-import styles from '@styles/pages/contact.module.scss';
import { sendMail } from '@services/graphql/contact';
+import styles from '@styles/pages/contact.module.scss';
import { type NextPageWithLayout } from '@ts/types/app';
import { loadTranslation } from '@utils/helpers/i18n';
+import {
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
import { GetStaticProps } from 'next';
@@ -18,7 +23,6 @@ import { useRouter } from 'next/router';
import Script from 'next/script';
import { useState } from 'react';
import { useIntl } from 'react-intl';
-import { ContactPage as ContactPageSchema, Graph, WebPage } from 'schema-dts';
const ContactPage: NextPageWithLayout = () => {
const { dates, intro, seo, title } = meta;
@@ -36,43 +40,23 @@ const ContactPage: NextPageWithLayout = () => {
const { website } = useSettings();
const { asPath } = useRouter();
- const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: seo.title,
+ const webpageSchema = getWebPageSchema({
description: seo.description,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${pageUrl}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const contactSchema: ContactPageSchema = {
- '@id': `${website.url}/#contact`,
- '@type': 'ContactPage',
- name: title,
+ locale: website.locales.default,
+ slug: asPath,
+ title: seo.title,
+ updateDate: dates.update,
+ });
+ const contactSchema = getSinglePageSchema({
+ dates,
description: intro,
- author: { '@id': `${website.url}/#branding` },
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- editor: { '@id': `${website.url}/#branding` },
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, contactSchema],
- };
+ id: 'contact',
+ kind: 'contact',
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, contactSchema]);
const widgets = [
<SocialMedia
@@ -134,7 +118,7 @@ const ContactPage: NextPageWithLayout = () => {
<Head>
<title>{`${seo.title} - ${website.name}`}</title>
<meta name="description" content={seo.description} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx
index 3f035d8..4686505 100644
--- a/src/pages/cv.tsx
+++ b/src/pages/cv.tsx
@@ -11,6 +11,11 @@ import CVContent, { data, meta } from '@content/pages/cv.mdx';
import styles from '@styles/pages/cv.module.scss';
import { type NextPageWithLayout } from '@ts/types/app';
import { loadTranslation } from '@utils/helpers/i18n';
+import {
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
import { NestedMDXComponents } from 'mdx/types';
@@ -20,7 +25,6 @@ import { useRouter } from 'next/router';
import Script from 'next/script';
import React, { ReactNode } from 'react';
import { useIntl } from 'react-intl';
-import { AboutPage, Graph, WebPage } from 'schema-dts';
/**
* CV page.
@@ -106,45 +110,24 @@ const CVPage: NextPageWithLayout = () => {
];
const { asPath } = useRouter();
- const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: seo.title,
+ const webpageSchema = getWebPageSchema({
description: seo.description,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${pageUrl}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const cvSchema: AboutPage = {
- '@id': `${website.url}/#cv`,
- '@type': 'AboutPage',
- name: seo.title,
+ locale: website.locales.default,
+ slug: asPath,
+ title: seo.title,
+ updateDate: dates.update,
+ });
+ const cvSchema = getSinglePageSchema({
+ cover: image.src,
+ dates,
description: intro,
- author: { '@id': `${website.url}/#branding` },
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- editor: { '@id': `${website.url}/#branding` },
- image: image.src,
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- thumbnailUrl: image.src,
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, cvSchema],
- };
+ id: 'cv',
+ kind: 'about',
+ locale: website.locales.default,
+ slug: asPath,
+ title: title,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, cvSchema]);
const components: NestedMDXComponents = {
a: (props) => <Link external={true} {...props} />,
@@ -171,7 +154,7 @@ const CVPage: NextPageWithLayout = () => {
<Head>
<title>{`${seo.title} - ${website.name}`}</title>
<meta name="description" content={seo.description} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index a831ea3..6e9c4c6 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -19,6 +19,7 @@ import { getArticlesCard } from '@services/graphql/articles';
import styles from '@styles/pages/home.module.scss';
import { type ArticleCard, type NextPageWithLayout } from '@ts/types/app';
import { loadTranslation, type Messages } from '@utils/helpers/i18n';
+import { getSchemaJson, getWebPageSchema } from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
import { NestedMDXComponents } from 'mdx/types';
@@ -27,7 +28,6 @@ import Head from 'next/head';
import Script from 'next/script';
import { ReactElement } from 'react';
import { useIntl } from 'react-intl';
-import { Graph, WebPage } from 'schema-dts';
type HomeProps = {
recentPosts: ArticleCard[];
@@ -306,25 +306,13 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => {
},
{ websiteName: website.name }
);
-
- const webpageSchema: WebPage = {
- '@id': `${website.url}/#home`,
- '@type': 'WebPage',
- name: pageTitle,
+ const webpageSchema = getWebPageSchema({
description: pageDescription,
- author: { '@id': `${website.url}/#branding` },
- creator: { '@id': `${website.url}/#branding` },
- editor: { '@id': `${website.url}/#branding` },
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${website.url}`,
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema],
- };
+ locale: website.locales.default,
+ slug: '',
+ title: pageTitle,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema]);
return (
<>
diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx
index c8d1772..a58a850 100644
--- a/src/pages/mentions-legales.tsx
+++ b/src/pages/mentions-legales.tsx
@@ -7,6 +7,11 @@ import PageLayout, {
import LegalNoticeContent, { meta } from '@content/pages/legal-notice.mdx';
import { type NextPageWithLayout } from '@ts/types/app';
import { loadTranslation } from '@utils/helpers/i18n';
+import {
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
import { NestedMDXComponents } from 'mdx/types';
@@ -14,7 +19,6 @@ import { GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
import Script from 'next/script';
-import { Article, Graph, WebPage } from 'schema-dts';
/**
* Legal Notice page.
@@ -44,47 +48,23 @@ const LegalNoticePage: NextPageWithLayout = () => {
const { website } = useSettings();
const { asPath } = useRouter();
- const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: seo.title,
+ const webpageSchema = getWebPageSchema({
description: seo.description,
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${pageUrl}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const articleSchema: Article = {
- '@id': `${website.url}/#legal-notice`,
- '@type': 'Article',
- name: title,
+ locale: website.locales.default,
+ slug: asPath,
+ title: seo.title,
+ updateDate: dates.update,
+ });
+ const articleSchema = getSinglePageSchema({
+ dates,
description: intro,
- author: { '@id': `${website.url}/#branding` },
- copyrightYear: pagePublicationDate.getFullYear(),
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- editor: { '@id': `${website.url}/#branding` },
- headline: title,
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, articleSchema],
- };
+ id: 'legal-notice',
+ kind: 'page',
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]);
return (
<PageLayout
@@ -98,7 +78,7 @@ const LegalNoticePage: NextPageWithLayout = () => {
<Head>
<title>{`${seo.title} - ${website.name}`}</title>
<meta name="description" content={seo.description} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={`${seo.title} - ${website.name}`} />
<meta property="og:description" content={intro} />
diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx
index 27c715d..247f350 100644
--- a/src/pages/projets/[slug].tsx
+++ b/src/pages/projets/[slug].tsx
@@ -22,6 +22,11 @@ import {
} from '@ts/types/app';
import { loadTranslation, type Messages } from '@utils/helpers/i18n';
import { getProjectData, getProjectFilenames } from '@utils/helpers/projects';
+import {
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import { capitalize } from '@utils/helpers/strings';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useGithubApi, { type RepoData } from '@utils/hooks/use-github-api';
@@ -33,7 +38,6 @@ import { useRouter } from 'next/router';
import Script from 'next/script';
import { ComponentType } from 'react';
import { useIntl } from 'react-intl';
-import { Article, Graph, WebPage } from 'schema-dts';
type ProjectPageProps = {
project: ProjectPreview;
@@ -66,8 +70,6 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => {
const { website } = useSettings();
const { asPath } = useRouter();
const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
const headerMeta: PageLayoutProps['headerMeta'] = {
publication: { date: dates.publication },
@@ -137,44 +139,24 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => {
technologies,
};
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: seo.title,
+ const webpageSchema = getWebPageSchema({
description: seo.description,
- inLanguage: website.locales.default,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${website.url}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const articleSchema: Article = {
- '@id': `${website.url}/project`,
- '@type': 'Article',
- name: title,
+ locale: website.locales.default,
+ slug: asPath,
+ title: seo.title,
+ updateDate: dates.update,
+ });
+ const articleSchema = getSinglePageSchema({
+ cover: `/projects/${id}.jpg`,
+ dates,
description: intro,
- author: { '@id': `${website.url}/#branding` },
- copyrightYear: pagePublicationDate.getFullYear(),
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- editor: { '@id': `${website.url}/#branding` },
- headline: title,
- thumbnailUrl: `/projects/${id}.jpg`,
- image: `/projects/${id}.jpg`,
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, articleSchema],
- };
+ id: 'project',
+ kind: 'page',
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]);
return (
<>
diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx
index 9ca289a..dbca019 100644
--- a/src/pages/projets/index.tsx
+++ b/src/pages/projets/index.tsx
@@ -9,6 +9,11 @@ import styles from '@styles/pages/projects.module.scss';
import { type NextPageWithLayout, type ProjectCard } from '@ts/types/app';
import { loadTranslation, type Messages } from '@utils/helpers/i18n';
import { getProjectsCard } from '@utils/helpers/projects';
+import {
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
import { NestedMDXComponents } from 'mdx/types';
@@ -16,7 +21,6 @@ import { GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
import Script from 'next/script';
-import { Article, Graph, WebPage } from 'schema-dts';
type ProjectsPageProps = {
projects: ProjectCard[];
@@ -54,54 +58,30 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => {
const { website } = useSettings();
const { asPath } = useRouter();
- const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: seo.title,
+ const webpageSchema = getWebPageSchema({
description: seo.description,
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${pageUrl}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const articleSchema: Article = {
- '@id': `${website.url}/#projects`,
- '@type': 'Article',
- name: meta.title,
+ locale: website.locales.default,
+ slug: asPath,
+ title: seo.title,
+ updateDate: dates.update,
+ });
+ const articleSchema = getSinglePageSchema({
+ dates,
description: seo.description,
- author: { '@id': `${website.url}/#branding` },
- copyrightYear: pagePublicationDate.getFullYear(),
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- editor: { '@id': `${website.url}/#branding` },
- headline: meta.title,
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, articleSchema],
- };
+ id: 'projects',
+ kind: 'page',
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]);
return (
<>
<Head>
<title>{`${seo.title} - ${website.name}`}</title>
<meta name="description" content={seo.description} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={`${seo.title} - ${website.name}`} />
<meta property="og:description" content={seo.description} />
diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx
index c69d931..ab619fb 100644
--- a/src/pages/recherche/index.tsx
+++ b/src/pages/recherche/index.tsx
@@ -22,6 +22,11 @@ import {
getPageLinkFromRawData,
getPostsList,
} from '@utils/helpers/pages';
+import {
+ getBlogSchema,
+ getSchemaJson,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useDataFromAPI from '@utils/hooks/use-data-from-api';
import usePagination from '@utils/hooks/use-pagination';
@@ -31,7 +36,6 @@ import Head from 'next/head';
import { useRouter } from 'next/router';
import Script from 'next/script';
import { useIntl } from 'react-intl';
-import { Blog, Graph, WebPage } from 'schema-dts';
type SearchPageProps = {
thematicsList: RawThematicPreview[];
@@ -87,37 +91,18 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({
},
{ websiteName: website.name }
);
- const pageUrl = `${website.url}${asPath}`;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: pageTitle,
+ const webpageSchema = getWebPageSchema({
description: pageDescription,
- inLanguage: website.locales.default,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${website.url}`,
- isPartOf: {
- '@id': `${website.url}`,
- },
- };
-
- const blogSchema: Blog = {
- '@id': `${website.url}/#blog`,
- '@type': 'Blog',
- author: { '@id': `${website.url}/#branding` },
- creator: { '@id': `${website.url}/#branding` },
- editor: { '@id': `${website.url}/#branding` },
- inLanguage: website.locales.default,
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, blogSchema],
- };
+ locale: website.locales.default,
+ slug: asPath,
+ title: pageTitle,
+ });
+ const blogSchema = getBlogSchema({
+ isSinglePage: false,
+ locale: website.locales.default,
+ slug: asPath,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]);
const {
data,
@@ -161,7 +146,7 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({
<Head>
<title>{pageTitle}</title>
<meta name="description" content={pageDescription} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:description" content={pageDescription} />
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index 95843cd..838f009 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -24,6 +24,11 @@ import {
getPageLinkFromRawData,
getPostsWithUrl,
} from '@utils/helpers/pages';
+import {
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
import { GetStaticPaths, GetStaticProps } from 'next';
@@ -32,7 +37,6 @@ import { useRouter } from 'next/router';
import Script from 'next/script';
import { ParsedUrlQuery } from 'querystring';
import { useIntl } from 'react-intl';
-import { Article as ArticleSchema, Graph, WebPage } from 'schema-dts';
export type TopicPageProps = {
currentTopic: Topic;
@@ -68,45 +72,24 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
const { website } = useSettings();
const { asPath } = useRouter();
- const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: seo.title,
+ const webpageSchema = getWebPageSchema({
description: seo.description,
- inLanguage: website.locales.default,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${website.url}`,
- };
-
- const articleSchema: ArticleSchema = {
- '@id': `${website.url}/#topic`,
- '@type': 'Article',
- name: title,
+ locale: website.locales.default,
+ slug: asPath,
+ title: seo.title,
+ updateDate: dates.update,
+ });
+ const articleSchema = getSinglePageSchema({
+ cover: cover?.src,
+ dates,
description: intro,
- author: { '@id': `${website.url}/#branding` },
- copyrightYear: pagePublicationDate.getFullYear(),
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- editor: { '@id': `${website.url}/#branding` },
- headline: title,
- inLanguage: website.locales.default,
- isPartOf: { '@id': `${website.url}/blog` },
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- subjectOf: { '@id': `${website.url}/blog` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, articleSchema],
- };
+ id: 'topic',
+ kind: 'page',
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]);
const topicsListTitle = intl.formatMessage({
defaultMessage: 'Other topics',
@@ -134,7 +117,7 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
<Head>
<title>{seo.title}</title>
<meta name="description" content={seo.description} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx
index a91483e..84ab134 100644
--- a/src/pages/thematique/[slug].tsx
+++ b/src/pages/thematique/[slug].tsx
@@ -22,6 +22,11 @@ import {
getPageLinkFromRawData,
getPostsWithUrl,
} from '@utils/helpers/pages';
+import {
+ getSchemaJson,
+ getSinglePageSchema,
+ getWebPageSchema,
+} from '@utils/helpers/schema-org';
import useBreadcrumb from '@utils/hooks/use-breadcrumb';
import useSettings from '@utils/hooks/use-settings';
import { GetStaticPaths, GetStaticProps } from 'next';
@@ -30,7 +35,6 @@ import { useRouter } from 'next/router';
import Script from 'next/script';
import { ParsedUrlQuery } from 'querystring';
import { useIntl } from 'react-intl';
-import { Article as ArticleSchema, Graph, WebPage } from 'schema-dts';
export type ThematicPageProps = {
currentThematic: Thematic;
@@ -58,45 +62,23 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({
const { website } = useSettings();
const { asPath } = useRouter();
- const pageUrl = `${website.url}${asPath}`;
- const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
-
- const webpageSchema: WebPage = {
- '@id': `${pageUrl}`,
- '@type': 'WebPage',
- breadcrumb: { '@id': `${website.url}/#breadcrumb` },
- name: seo.title,
+ const webpageSchema = getWebPageSchema({
description: seo.description,
- inLanguage: website.locales.default,
- reviewedBy: { '@id': `${website.url}/#branding` },
- url: `${website.url}`,
- };
-
- const articleSchema: ArticleSchema = {
- '@id': `${website.url}/#thematic`,
- '@type': 'Article',
- name: title,
+ locale: website.locales.default,
+ slug: asPath,
+ title: seo.title,
+ updateDate: dates.update,
+ });
+ const articleSchema = getSinglePageSchema({
+ dates,
description: intro,
- author: { '@id': `${website.url}/#branding` },
- copyrightYear: pagePublicationDate.getFullYear(),
- creator: { '@id': `${website.url}/#branding` },
- dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
- datePublished: pagePublicationDate.toISOString(),
- editor: { '@id': `${website.url}/#branding` },
- headline: title,
- inLanguage: website.locales.default,
- isPartOf: { '@id': `${website.url}/blog` },
- license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
- mainEntityOfPage: { '@id': `${pageUrl}` },
- subjectOf: { '@id': `${website.url}/blog` },
- };
-
- const schemaJsonLd: Graph = {
- '@context': 'https://schema.org',
- '@graph': [webpageSchema, articleSchema],
- };
+ id: 'thematic',
+ kind: 'page',
+ locale: website.locales.default,
+ slug: asPath,
+ title,
+ });
+ const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]);
const thematicsListTitle = intl.formatMessage({
defaultMessage: 'Other thematics',
@@ -115,7 +97,7 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({
<Head>
<title>{seo.title}</title>
<meta name="description" content={seo.description} />
- <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:url" content={`${website.url}${asPath}`} />
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
diff --git a/src/utils/helpers/schema-org.ts b/src/utils/helpers/schema-org.ts
new file mode 100644
index 0000000..cdace00
--- /dev/null
+++ b/src/utils/helpers/schema-org.ts
@@ -0,0 +1,224 @@
+import { Dates } from '@ts/types/app';
+import { settings } from '@utils/config';
+import {
+ AboutPage,
+ Article,
+ Blog,
+ BlogPosting,
+ ContactPage,
+ Graph,
+ WebPage,
+} from 'schema-dts';
+
+export type GetBlogSchemaProps = {
+ /**
+ * True if the page is part of the blog.
+ */
+ isSinglePage: boolean;
+ /**
+ * The page locale.
+ */
+ locale: string;
+ /**
+ * The page slug with a leading slash.
+ */
+ slug: string;
+};
+
+/**
+ * Retrieve the JSON for Blog schema.
+ *
+ * @param props - The page data.
+ * @returns {Blog} The JSON for Blog schema.
+ */
+export const getBlogSchema = ({
+ isSinglePage,
+ locale,
+ slug,
+}: GetBlogSchemaProps): Blog => {
+ return {
+ '@id': `${settings.url}/#blog`,
+ '@type': 'Blog',
+ author: { '@id': `${settings.url}/#branding` },
+ creator: { '@id': `${settings.url}/#branding` },
+ editor: { '@id': `${settings.url}/#branding` },
+ blogPost: isSinglePage ? { '@id': `${settings.url}/#article` } : undefined,
+ inLanguage: locale,
+ isPartOf: isSinglePage
+ ? {
+ '@id': `${settings.url}${slug}`,
+ }
+ : undefined,
+ license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
+ mainEntityOfPage: isSinglePage
+ ? undefined
+ : { '@id': `${settings.url}${slug}` },
+ };
+};
+
+export type SinglePageSchemaReturn = {
+ about: AboutPage;
+ contact: ContactPage;
+ page: Article;
+ post: BlogPosting;
+};
+
+export type SinglePageSchemaKind = keyof SinglePageSchemaReturn;
+
+export type GetSinglePageSchemaProps<T extends SinglePageSchemaKind> = {
+ /**
+ * The number of comments.
+ */
+ commentsCount?: number;
+ /**
+ * The page content.
+ */
+ content?: string;
+ /**
+ * The url of the cover.
+ */
+ cover?: string;
+ /**
+ * The page dates.
+ */
+ dates: Dates;
+ /**
+ * The page description.
+ */
+ description: string;
+ /**
+ * The page id.
+ */
+ id: string;
+ /**
+ * The page kind.
+ */
+ kind: T;
+ /**
+ * The page locale.
+ */
+ locale: string;
+ /**
+ * The page slug with a leading slash.
+ */
+ slug: string;
+ /**
+ * The page title.
+ */
+ title: string;
+};
+
+/**
+ * Retrieve the JSON schema depending on the page kind.
+ *
+ * @param props - The page data.
+ * @returns {SinglePageSchemaReturn[T]} - Either AboutPage, ContactPage, Article or BlogPosting schema.
+ */
+export const getSinglePageSchema = <T extends SinglePageSchemaKind>({
+ commentsCount,
+ content,
+ cover,
+ dates,
+ description,
+ id,
+ kind,
+ locale,
+ title,
+ slug,
+}: GetSinglePageSchemaProps<T>): SinglePageSchemaReturn[T] => {
+ const publicationDate = new Date(dates.publication);
+ const updateDate = dates.update ? new Date(dates.update) : undefined;
+ const singlePageSchemaType = {
+ about: 'AboutPage',
+ contact: 'ContactPage',
+ page: 'Article',
+ post: 'BlogPosting',
+ };
+
+ return {
+ '@id': `${settings.url}/#${id}`,
+ '@type': singlePageSchemaType[kind],
+ name: title,
+ description,
+ articleBody: content,
+ author: { '@id': `${settings.url}/#branding` },
+ commentCount: commentsCount,
+ copyrightYear: publicationDate.getFullYear(),
+ creator: { '@id': `${settings.url}/#branding` },
+ dateCreated: publicationDate.toISOString(),
+ dateModified: updateDate && updateDate.toISOString(),
+ datePublished: publicationDate.toISOString(),
+ editor: { '@id': `${settings.url}/#branding` },
+ headline: title,
+ image: cover,
+ inLanguage: locale,
+ license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
+ thumbnailUrl: cover,
+ isPartOf:
+ kind === 'post'
+ ? {
+ '@id': `${settings.url}/blog`,
+ }
+ : undefined,
+ mainEntityOfPage: { '@id': `${settings.url}${slug}` },
+ } as SinglePageSchemaReturn[T];
+};
+
+export type GetWebPageSchemaProps = {
+ /**
+ * The page description.
+ */
+ description: string;
+ /**
+ * The page locale.
+ */
+ locale: string;
+ /**
+ * The page slug.
+ */
+ slug: string;
+ /**
+ * The page title.
+ */
+ title: string;
+ /**
+ * The page last update.
+ */
+ updateDate?: string;
+};
+
+/**
+ * Retrieve the JSON for WebPage schema.
+ *
+ * @param props - The page data.
+ * @returns {WebPage} The JSON for WebPage schema.
+ */
+export const getWebPageSchema = ({
+ description,
+ locale,
+ slug,
+ title,
+ updateDate,
+}: GetWebPageSchemaProps): WebPage => {
+ return {
+ '@id': `${settings.url}${slug}`,
+ '@type': 'WebPage',
+ breadcrumb: { '@id': `${settings.url}/#breadcrumb` },
+ lastReviewed: updateDate,
+ name: title,
+ description: description,
+ inLanguage: locale,
+ reviewedBy: { '@id': `${settings.url}/#branding` },
+ url: `${settings.url}${slug}`,
+ isPartOf: {
+ '@id': `${settings.url}`,
+ },
+ };
+};
+
+export const getSchemaJson = (graphs: Graph['@graph']): Graph => {
+ return {
+ '@context': 'https://schema.org',
+ '@graph': graphs,
+ };
+};