aboutsummaryrefslogtreecommitdiffstats
path: root/src/pages
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-12-14 15:30:34 +0100
committerArmand Philippot <git@armandphilippot.com>2023-12-14 16:30:04 +0100
commit7063b199b4748a9c354ed37e64cdc84c512f2c0c (patch)
tree7506c3003c56b49a248e9adb40be610780bb540e /src/pages
parent85c4c42bd601270d7be0f34a0767a34bb85e29bb (diff)
refactor(pages): rewrite helpers to output schema in json-ld format
* make sure url are absolutes * nest breadcrumb schema in webpage schema * trim HTML tags from content/description * use a regular script instead of next/script (with the latter the schema is not updated on route change) * place the script in document head * add keywords, wordCount and readingTime keys in BlogPosting schema * fix breadcrumbs in search page (without query) * add tests (a `MatchInlineSnapshot` will be better but Prettier 3 is not supported yet)
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/404.tsx27
-rw-r--r--src/pages/article/[slug].tsx100
-rw-r--r--src/pages/blog/index.tsx58
-rw-r--r--src/pages/blog/page/[number].tsx57
-rw-r--r--src/pages/contact.tsx47
-rw-r--r--src/pages/cv.tsx57
-rw-r--r--src/pages/index.tsx44
-rw-r--r--src/pages/mentions-legales.tsx47
-rw-r--r--src/pages/projets/[slug].tsx50
-rw-r--r--src/pages/projets/index.tsx50
-rw-r--r--src/pages/recherche/index.tsx57
-rw-r--r--src/pages/sujet/[slug].tsx50
-rw-r--r--src/pages/thematique/[slug].tsx48
13 files changed, 309 insertions, 383 deletions
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 450859c..72c252e 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,7 +1,6 @@
import type { GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
-import Script from 'next/script';
import { useCallback, type ReactNode } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -34,7 +33,11 @@ import type {
} from '../types';
import { CONFIG } from '../utils/config';
import { ROUTES } from '../utils/constants';
-import { getLinksItemData } from '../utils/helpers';
+import {
+ getLinksItemData,
+ getSchemaFrom,
+ getWebPageGraph,
+} from '../utils/helpers';
import { loadTranslation, type Messages } from '../utils/helpers/server';
import {
useBreadcrumbs,
@@ -118,6 +121,15 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({ data }) => {
messages.page.title
);
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ description: messages.seo.metaDesc,
+ slug: ROUTES.NOT_FOUND,
+ title: messages.page.title,
+ }),
+ ]);
+
const searchSubmitHandler: SearchFormSubmit = useCallback(
async ({ query }) => {
if (!query)
@@ -145,13 +157,12 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({ data }) => {
<title>{messages.seo.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
<meta name="description" content={messages.seo.metaDesc} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-breadcrumb"
- type="application/ld+json"
- />
<PageHeader heading={messages.page.title} />
<PageBody className={styles['no-results']}>
<p>
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 6333056..e18de75 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -3,7 +3,6 @@ import type { ParsedUrlQuery } from 'querystring';
import type { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
-import Script from 'next/script';
import { useCallback } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -36,11 +35,12 @@ import type {
} from '../../types';
import { CONFIG } from '../../utils/config';
import {
- getBlogSchema,
- getCommentsSchema,
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
+ getBlogPostingGraph,
+ getCommentGraph,
+ getReadingTimeFrom,
+ getSchemaFrom,
+ getWebPageGraph,
+ trimHTMLTags,
updateWordPressCodeBlocks,
} from '../../utils/helpers';
import { loadTranslation, type Messages } from '../../utils/helpers/server';
@@ -129,9 +129,17 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ data }) => {
[intl]
);
+ const flattenComments = useCallback(
+ (allComments: SingleComment[]): SingleComment[] => [
+ ...allComments,
+ ...allComments.flatMap((comment) => flattenComments(comment.replies)),
+ ],
+ []
+ );
+
if (isFallback || isLoading) return <LoadingPage />;
- const { content, id, intro, meta, title } = article;
+ const { content, id, intro, meta, slug, title } = article;
const {
author,
commentsCount,
@@ -143,36 +151,42 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ data }) => {
wordsCount,
} = meta;
- const webpageSchema = getWebPageSchema({
- description: intro,
- locale: CONFIG.locales.defaultLocale,
- slug: article.slug,
- title,
- updateDate: dates.update,
- });
- const blogSchema = getBlogSchema({
- isSinglePage: true,
- locale: CONFIG.locales.defaultLocale,
- slug: article.slug,
- });
- const blogPostSchema = getSinglePageSchema({
- commentsCount,
- content,
- cover: cover?.src,
- dates,
- description: intro,
- id: 'article',
- kind: 'post',
- locale: CONFIG.locales.defaultLocale,
- slug: article.slug,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- blogSchema,
- blogPostSchema,
- breadcrumbSchema,
- ...getCommentsSchema(comments),
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ dates,
+ description: trimHTMLTags(intro),
+ slug,
+ title,
+ }),
+ getBlogPostingGraph({
+ body: trimHTMLTags(content),
+ comment: flattenComments(comments).map((comment) =>
+ getCommentGraph({
+ articleSlug: slug,
+ author: {
+ '@type': 'Person',
+ name: comment.meta.author.name,
+ url: comment.meta.author.website,
+ },
+ body: trimHTMLTags(comment.content),
+ id: `${comment.id}`,
+ parentId: comment.parentId ? `${comment.parentId}` : undefined,
+ publishedAt: comment.meta.date,
+ })
+ ),
+ commentCount: commentsCount,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ cover: cover?.src,
+ dates,
+ description: trimHTMLTags(intro),
+ keywords: topics?.map((topic) => topic.name).join(', '),
+ readingTime: `PT${getReadingTimeFrom(wordsCount).inMinutes()}M`,
+ slug,
+ title,
+ wordCount: meta.wordsCount,
+ }),
]);
const pageUrl = `${CONFIG.url}${article.slug}`;
@@ -200,14 +214,12 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ data }) => {
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-article"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={title}
intro={intro}
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 49c16b1..f58d36f 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -1,7 +1,5 @@
-/* eslint-disable max-statements */
import type { GetStaticProps } from 'next';
import Head from 'next/head';
-import Script from 'next/script';
import { useCallback } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -37,13 +35,17 @@ import type {
WPTopicPreview,
} from '../../types';
import { CONFIG } from '../../utils/config';
-import { PAGINATED_ROUTE_PREFIX, ROUTES } from '../../utils/constants';
import {
- getBlogSchema,
+ ARTICLE_ID,
+ PAGINATED_ROUTE_PREFIX,
+ ROUTES,
+} from '../../utils/constants';
+import {
+ getBlogGraph,
getLinksItemData,
getPostsWithUrl,
- getSchemaJson,
- getWebPageSchema,
+ getSchemaFrom,
+ getWebPageGraph,
} from '../../utils/helpers';
import { loadTranslation, type Messages } from '../../utils/helpers/server';
import {
@@ -160,21 +162,23 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ data }) => {
messages.pageTitle
);
- const webpageSchema = getWebPageSchema({
- description: messages.seo.metaDesc,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.BLOG,
- title: messages.pageTitle,
- });
- const blogSchema = getBlogSchema({
- isSinglePage: false,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.BLOG,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- blogSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ description: messages.seo.metaDesc,
+ slug: ROUTES.BLOG,
+ title: messages.pageTitle,
+ }),
+ getBlogGraph({
+ description: '',
+ posts: articles?.flatMap((page) =>
+ page.edges.map(({ node }) => {
+ return { '@id': `${node.slug}#${ARTICLE_ID}` };
+ })
+ ),
+ slug: ROUTES.BLOG,
+ title: messages.pageTitle,
+ }),
]);
const renderPaginationLabel: RenderPaginationItemAriaLabel = useCallback(
@@ -235,14 +239,12 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ data }) => {
<meta property="og:type" content="website" />
<meta property="og:title" content={messages.pageTitle} />
<meta property="og:description" content={messages.seo.metaDesc} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-blog"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={messages.pageTitle}
meta={{ total: data.posts.pageInfo.total }}
diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx
index 906a08e..fa1123d 100644
--- a/src/pages/blog/page/[number].tsx
+++ b/src/pages/blog/page/[number].tsx
@@ -3,7 +3,6 @@ import type { ParsedUrlQuery } from 'querystring';
import type { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
-import Script from 'next/script';
import { useCallback } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -44,13 +43,17 @@ import type {
WPTopicPreview,
} from '../../../types';
import { CONFIG } from '../../../utils/config';
-import { PAGINATED_ROUTE_PREFIX, ROUTES } from '../../../utils/constants';
import {
- getBlogSchema,
+ ARTICLE_ID,
+ PAGINATED_ROUTE_PREFIX,
+ ROUTES,
+} from '../../../utils/constants';
+import {
+ getBlogGraph,
getLinksItemData,
getPostsWithUrl,
- getSchemaJson,
- getWebPageSchema,
+ getSchemaFrom,
+ getWebPageGraph,
} from '../../../utils/helpers';
import { loadTranslation, type Messages } from '../../../utils/helpers/server';
import {
@@ -189,21 +192,23 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
messages.pageTitle
);
- const webpageSchema = getWebPageSchema({
- description: messages.seo.metaDesc,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.BLOG,
- title: messages.pageTitle,
- });
- const blogSchema = getBlogSchema({
- isSinglePage: false,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.BLOG,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- blogSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ description: messages.seo.metaDesc,
+ slug: ROUTES.BLOG,
+ title: messages.pageTitle,
+ }),
+ getBlogGraph({
+ description: '',
+ posts: articles?.flatMap((page) =>
+ page.edges.map(({ node }) => {
+ return { '@id': `${node.slug}#${ARTICLE_ID}` };
+ })
+ ),
+ slug: ROUTES.BLOG,
+ title: messages.pageTitle,
+ }),
]);
const renderPaginationLabel: RenderPaginationItemAriaLabel = useCallback(
@@ -266,14 +271,12 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
<meta property="og:type" content="website" />
<meta property="og:title" content={messages.pageTitle} />
<meta property="og:description" content={messages.seo.metaDesc} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-blog"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={messages.pageTitle}
meta={{ total: data.posts.pageInfo.total }}
diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx
index 264ca56..7cf17f0 100644
--- a/src/pages/contact.tsx
+++ b/src/pages/contact.tsx
@@ -1,6 +1,5 @@
import type { GetStaticProps } from 'next';
import Head from 'next/head';
-import Script from 'next/script';
import { useCallback } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -19,11 +18,7 @@ import { sendEmail } from '../services/graphql';
import type { NextPageWithLayout } from '../types';
import { CONFIG } from '../utils/config';
import { ROUTES } from '../utils/constants';
-import {
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
-} from '../utils/helpers';
+import { getContactPageGraph, getSchemaFrom } from '../utils/helpers';
import { loadTranslation } from '../utils/helpers/server';
import { useBreadcrumbs } from '../utils/hooks';
@@ -65,26 +60,15 @@ const ContactPage: NextPageWithLayout = () => {
},
};
- const webpageSchema = getWebPageSchema({
- description: seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.CONTACT,
- title: seo.title,
- updateDate: dates.update,
- });
- const contactSchema = getSinglePageSchema({
- dates,
- description: intro,
- id: 'contact',
- kind: 'contact',
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.CONTACT,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- contactSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getContactPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ dates,
+ description: intro,
+ slug: ROUTES.CONTACT,
+ title,
+ }),
]);
const submitMail: ContactFormSubmit = useCallback(
@@ -143,13 +127,12 @@ const ContactPage: NextPageWithLayout = () => {
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-contact"
- type="application/ld+json"
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader heading={title} intro={intro} />
<PageBody>
<ContactForm aria-label={messages.form} onSubmit={submitMail} />
diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx
index d08c121..92c3e9e 100644
--- a/src/pages/cv.tsx
+++ b/src/pages/cv.tsx
@@ -1,9 +1,6 @@
-/* eslint-disable max-statements */
import type { GetStaticProps } from 'next';
import Head from 'next/head';
import NextImage from 'next/image';
-import { useRouter } from 'next/router';
-import Script from 'next/script';
import React, { type ReactNode } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -22,12 +19,8 @@ import { mdxComponents } from '../components/mdx';
import CVContent, { data, meta } from '../content/pages/cv.mdx';
import type { NextPageWithLayout } from '../types';
import { CONFIG } from '../utils/config';
-import { PERSONAL_LINKS } from '../utils/constants';
-import {
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
-} from '../utils/helpers';
+import { PERSONAL_LINKS, ROUTES } from '../utils/constants';
+import { getAboutPageGraph, getSchemaFrom } from '../utils/helpers';
import { loadTranslation } from '../utils/helpers/server';
import { useBreadcrumbs, useHeadingsTree } from '../utils/hooks';
@@ -95,32 +88,21 @@ const CVPage: NextPageWithLayout = () => {
},
};
- const { asPath } = useRouter();
- const webpageSchema = getWebPageSchema({
- description: seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug: asPath,
- title: seo.title,
- updateDate: dates.update,
- });
- const cvSchema = getSinglePageSchema({
- cover: data.image.src,
- dates,
- description: intro,
- id: 'cv',
- kind: 'about',
- locale: CONFIG.locales.defaultLocale,
- slug: asPath,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- cvSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getAboutPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ cover: data.image.src,
+ dates,
+ description: intro,
+ slug: ROUTES.CV,
+ title,
+ }),
]);
+
const page = {
title: `${seo.title} - ${CONFIG.name}`,
- url: `${CONFIG.url}${asPath}`,
+ url: `${CONFIG.url}${ROUTES.CV}`,
};
return (
@@ -136,13 +118,12 @@ const CVPage: NextPageWithLayout = () => {
<meta property="og:description" content={intro} />
<meta property="og:image" content={data.image.src} />
<meta property="og:image:alt" content={title} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-cv"
- type="application/ld+json"
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={title}
intro={intro}
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index ade628a..0e6bb23 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -2,7 +2,6 @@ import type { MDXComponents } from 'mdx/types';
import type { GetStaticProps } from 'next';
import Head from 'next/head';
import NextImage from 'next/image';
-import Script from 'next/script';
import type { FC } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -27,7 +26,11 @@ import {
import type { NextPageWithLayout, RecentArticle } from '../types';
import { CONFIG } from '../utils/config';
import { ROUTES } from '../utils/constants';
-import { getSchemaJson, getWebPageSchema } from '../utils/helpers';
+import {
+ getSchemaFrom,
+ getWebPageGraph,
+ getWebSiteGraph,
+} from '../utils/helpers';
import { loadTranslation, type Messages } from '../utils/helpers/server';
import { useBreadcrumbs } from '../utils/hooks';
@@ -129,15 +132,29 @@ type HomeProps = {
* Home page.
*/
const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => {
+ const intl = useIntl();
const { schema: breadcrumbSchema } = useBreadcrumbs();
- const webpageSchema = getWebPageSchema({
- description: meta.seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.HOME,
- title: meta.seo.title,
+ const pageTitle = intl.formatMessage({
+ defaultMessage: 'Home',
+ description: 'HomePage: page title',
+ id: 'j3+hB9',
});
- const schemaJsonLd = getSchemaJson([webpageSchema, breadcrumbSchema]);
+
+ const jsonLd = getSchemaFrom([
+ getWebSiteGraph({
+ description: CONFIG.baseline,
+ title: CONFIG.name,
+ }),
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(meta.dates.publication).getFullYear(),
+ dates: meta.dates,
+ description: meta.seo.description,
+ slug: ROUTES.HOME,
+ title: pageTitle,
+ }),
+ ]);
return (
<Page hasSections>
@@ -148,13 +165,12 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => {
<meta property="og:url" content={CONFIG.url} />
<meta property="og:title" content={meta.seo.title} />
<meta property="og:description" content={meta.seo.description} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-homepage"
- type="application/ld+json"
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<HomePageContent components={getComponents(recentPosts)} />
</Page>
);
diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx
index 8613898..13fd919 100644
--- a/src/pages/mentions-legales.tsx
+++ b/src/pages/mentions-legales.tsx
@@ -1,6 +1,5 @@
import type { GetStaticProps } from 'next';
import Head from 'next/head';
-import Script from 'next/script';
import { useIntl } from 'react-intl';
import {
getLayout,
@@ -16,11 +15,7 @@ import LegalNoticeContent, { meta } from '../content/pages/legal-notice.mdx';
import type { NextPageWithLayout } from '../types';
import { CONFIG } from '../utils/config';
import { ROUTES } from '../utils/constants';
-import {
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
-} from '../utils/helpers';
+import { getSchemaFrom, getWebPageGraph } from '../utils/helpers';
import { loadTranslation } from '../utils/helpers/server';
import { useBreadcrumbs, useHeadingsTree } from '../utils/hooks';
@@ -34,26 +29,15 @@ const LegalNoticePage: NextPageWithLayout = () => {
useBreadcrumbs(title);
const { ref, tree } = useHeadingsTree<HTMLDivElement>({ fromLevel: 2 });
- const webpageSchema = getWebPageSchema({
- description: seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.LEGAL_NOTICE,
- title: seo.title,
- updateDate: dates.update,
- });
- const articleSchema = getSinglePageSchema({
- dates,
- description: intro,
- id: 'legal-notice',
- kind: 'page',
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.LEGAL_NOTICE,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- articleSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ dates,
+ description: intro,
+ slug: ROUTES.LEGAL_NOTICE,
+ title,
+ }),
]);
const page = {
@@ -77,13 +61,12 @@ const LegalNoticePage: NextPageWithLayout = () => {
<meta property="og:type" content="article" />
<meta property="og:title" content={page.title} />
<meta property="og:description" content={intro} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-legal-notice"
- type="application/ld+json"
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={title}
intro={intro}
diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx
index 8985f47..1f9723a 100644
--- a/src/pages/projets/[slug].tsx
+++ b/src/pages/projets/[slug].tsx
@@ -1,10 +1,8 @@
-/* eslint-disable max-statements */
import type { MDXComponents } from 'mdx/types';
import type { GetStaticPaths, GetStaticProps } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import NextImage from 'next/image';
-import Script from 'next/script';
import { useMemo, type ComponentType, type FC } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -38,9 +36,8 @@ import type {
import { CONFIG } from '../../utils/config';
import {
capitalize,
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
+ getSchemaFrom,
+ getWebPageGraph,
} from '../../utils/helpers';
import {
type Messages,
@@ -192,27 +189,16 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ data }) => {
url: `${CONFIG.url}${slug}`,
};
- const webpageSchema = getWebPageSchema({
- description: meta.seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug,
- title: meta.seo.title,
- updateDate: meta.dates.update,
- });
- const articleSchema = getSinglePageSchema({
- cover: `/projects/${id}.jpg`,
- dates: meta.dates,
- description: intro,
- id: 'project',
- kind: 'page',
- locale: CONFIG.locales.defaultLocale,
- slug,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- articleSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(meta.dates.publication).getFullYear(),
+ cover: `/projects/${id}.jpg`,
+ dates: meta.dates,
+ description: intro,
+ slug,
+ title,
+ }),
]);
const messages = {
@@ -256,14 +242,12 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ data }) => {
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-project"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={title}
intro={intro}
diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx
index 401c68c..3815370 100644
--- a/src/pages/projets/index.tsx
+++ b/src/pages/projets/index.tsx
@@ -1,7 +1,6 @@
import type { GetStaticProps } from 'next';
import Head from 'next/head';
import NextImage from 'next/image';
-import Script from 'next/script';
import { useIntl } from 'react-intl';
import {
Card,
@@ -25,11 +24,7 @@ import styles from '../../styles/pages/projects.module.scss';
import type { NextPageWithLayout, ProjectPreview } from '../../types';
import { CONFIG } from '../../utils/config';
import { ROUTES } from '../../utils/constants';
-import {
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
-} from '../../utils/helpers';
+import { getSchemaFrom, getWebPageGraph } from '../../utils/helpers';
import {
getAllProjects,
loadTranslation,
@@ -52,27 +47,18 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ data }) => {
const { items: breadcrumbItems, schema: breadcrumbSchema } =
useBreadcrumbs(title);
const intl = useIntl();
- const webpageSchema = getWebPageSchema({
- description: seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.PROJECTS,
- title: seo.title,
- updateDate: dates.update,
- });
- const articleSchema = getSinglePageSchema({
- dates,
- description: seo.description,
- id: 'projects',
- kind: 'page',
- locale: CONFIG.locales.defaultLocale,
- slug: ROUTES.PROJECTS,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- articleSchema,
- breadcrumbSchema,
+
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ dates,
+ description: seo.description,
+ slug: ROUTES.PROJECTS,
+ title,
+ }),
]);
+
const page = {
title: `${seo.title} - ${CONFIG.name}`,
url: `${CONFIG.url}${ROUTES.PROJECTS}`,
@@ -89,14 +75,12 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ data }) => {
<meta property="og:type" content="article" />
<meta property="og:title" content={page.title} />
<meta property="og:description" content={seo.description} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-projects"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={title}
intro={<PageContent components={mdxComponents} />}
diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx
index fd7f9e1..84e75af 100644
--- a/src/pages/recherche/index.tsx
+++ b/src/pages/recherche/index.tsx
@@ -1,8 +1,6 @@
-/* eslint-disable max-statements */
import type { GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
-import Script from 'next/script';
import { useCallback } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -37,11 +35,11 @@ import type {
import { CONFIG } from '../../utils/config';
import { ROUTES } from '../../utils/constants';
import {
- getBlogSchema,
getLinksItemData,
getPostsWithUrl,
- getSchemaJson,
- getWebPageSchema,
+ getSchemaFrom,
+ getSearchResultsPageGraph,
+ getWebPageGraph,
} from '../../utils/helpers';
import { loadTranslation, type Messages } from '../../utils/helpers/server';
import {
@@ -165,17 +163,17 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ data }) => {
? intl.formatMessage(
{
defaultMessage:
- 'Discover search results for {query} on {websiteName}.',
+ 'Discover search results for {query} on {websiteName} website.',
description: 'SearchPage: SEO - Meta description',
- id: 'pg26sn',
+ id: 'bW6Zda',
},
{ query: query.s as string, websiteName: CONFIG.name }
)
: intl.formatMessage(
{
- defaultMessage: 'Search for a post on {websiteName}.',
+ defaultMessage: 'Search for a post on {websiteName} website.',
description: 'SearchPage: SEO - Meta description',
- id: 'npisb3',
+ id: 'rEp1mS',
},
{ websiteName: CONFIG.name }
),
@@ -213,21 +211,20 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ data }) => {
const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumbs();
- const webpageSchema = getWebPageSchema({
- description: messages.seo.metaDesc,
- locale: CONFIG.locales.defaultLocale,
- slug: asPath,
- title: messages.pageTitle,
- });
- const blogSchema = getBlogSchema({
- isSinglePage: false,
- locale: CONFIG.locales.defaultLocale,
- slug: asPath,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- blogSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ query.s
+ ? getSearchResultsPageGraph({
+ breadcrumb: breadcrumbSchema,
+ description: messages.seo.metaDesc,
+ slug: asPath,
+ title: messages.pageTitle,
+ })
+ : getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ description: messages.seo.metaDesc,
+ slug: asPath,
+ title: messages.pageTitle,
+ }),
]);
const pageUrl = `${CONFIG.url}${asPath}`;
@@ -243,14 +240,12 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ data }) => {
<meta property="og:type" content="website" />
<meta property="og:title" content={messages.pageTitle} />
<meta property="og:description" content={messages.seo.title} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-blog"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={messages.pageTitle}
meta={{ total: articles ? articles[0].pageInfo.total : undefined }}
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index 9d42644..af78185 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -4,7 +4,6 @@ import type { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
import NextImage from 'next/image';
import { useRouter } from 'next/router';
-import Script from 'next/script';
import { useIntl } from 'react-intl';
import {
getLayout,
@@ -37,10 +36,10 @@ import { CONFIG } from '../../utils/config';
import {
getLinksItemData,
getPostsWithUrl,
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
+ getSchemaFrom,
+ getWebPageGraph,
slugify,
+ trimHTMLTags,
} from '../../utils/helpers';
import { loadTranslation, type Messages } from '../../utils/helpers/server';
import {
@@ -87,27 +86,16 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ data }) => {
website: officialWebsite,
} = meta;
- const webpageSchema = getWebPageSchema({
- description: seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug,
- title: seo.title,
- updateDate: dates.update,
- });
- const articleSchema = getSinglePageSchema({
- cover: cover?.src,
- dates,
- description: intro,
- id: 'topic',
- kind: 'page',
- locale: CONFIG.locales.defaultLocale,
- slug,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- articleSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ cover: cover?.src,
+ dates,
+ description: trimHTMLTags(intro),
+ slug,
+ title,
+ }),
]);
const messages = {
@@ -157,14 +145,12 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ data }) => {
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-project"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={
<>
diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx
index f019341..56b956f 100644
--- a/src/pages/thematique/[slug].tsx
+++ b/src/pages/thematique/[slug].tsx
@@ -3,7 +3,6 @@ import type { ParsedUrlQuery } from 'querystring';
import type { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
-import Script from 'next/script';
import { useIntl } from 'react-intl';
import {
getLayout,
@@ -36,10 +35,10 @@ import { CONFIG } from '../../utils/config';
import {
getLinksItemData,
getPostsWithUrl,
- getSchemaJson,
- getSinglePageSchema,
- getWebPageSchema,
+ getSchemaFrom,
+ getWebPageGraph,
slugify,
+ trimHTMLTags,
} from '../../utils/helpers';
import { loadTranslation, type Messages } from '../../utils/helpers/server';
import {
@@ -79,26 +78,15 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ data }) => {
const { content, intro, meta, slug, title } = thematic;
const { articles, dates, seo, relatedTopics } = meta;
- const webpageSchema = getWebPageSchema({
- description: seo.description,
- locale: CONFIG.locales.defaultLocale,
- slug,
- title: seo.title,
- updateDate: dates.update,
- });
- const articleSchema = getSinglePageSchema({
- dates,
- description: intro,
- id: 'thematic',
- kind: 'page',
- locale: CONFIG.locales.defaultLocale,
- slug,
- title,
- });
- const schemaJsonLd = getSchemaJson([
- webpageSchema,
- articleSchema,
- breadcrumbSchema,
+ const jsonLd = getSchemaFrom([
+ getWebPageGraph({
+ breadcrumb: breadcrumbSchema,
+ copyrightYear: new Date(dates.publication).getFullYear(),
+ dates,
+ description: trimHTMLTags(intro),
+ slug,
+ title,
+ }),
]);
const messages = {
@@ -148,14 +136,12 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ data }) => {
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={intro} />
+ <script
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
+ type="application/ld+json"
+ />
</Head>
- <Script
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed
- id="schema-project"
- type="application/ld+json"
- // eslint-disable-next-line react/no-danger -- Necessary for schema
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- />
<PageHeader
heading={title}
intro={intro}