diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-12-14 15:30:34 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-12-14 16:30:04 +0100 |
| commit | 7063b199b4748a9c354ed37e64cdc84c512f2c0c (patch) | |
| tree | 7506c3003c56b49a248e9adb40be610780bb540e /src/pages | |
| parent | 85c4c42bd601270d7be0f34a0767a34bb85e29bb (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.tsx | 27 | ||||
| -rw-r--r-- | src/pages/article/[slug].tsx | 100 | ||||
| -rw-r--r-- | src/pages/blog/index.tsx | 58 | ||||
| -rw-r--r-- | src/pages/blog/page/[number].tsx | 57 | ||||
| -rw-r--r-- | src/pages/contact.tsx | 47 | ||||
| -rw-r--r-- | src/pages/cv.tsx | 57 | ||||
| -rw-r--r-- | src/pages/index.tsx | 44 | ||||
| -rw-r--r-- | src/pages/mentions-legales.tsx | 47 | ||||
| -rw-r--r-- | src/pages/projets/[slug].tsx | 50 | ||||
| -rw-r--r-- | src/pages/projets/index.tsx | 50 | ||||
| -rw-r--r-- | src/pages/recherche/index.tsx | 57 | ||||
| -rw-r--r-- | src/pages/sujet/[slug].tsx | 50 | ||||
| -rw-r--r-- | src/pages/thematique/[slug].tsx | 48 |
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} |
