diff --git a/.gitignore b/.gitignore index 67c78f64..db8a5aa1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ package-lock.json lib notes.md .DS_Store +.idea diff --git a/README.md b/README.md index 15ca910f..ea129751 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ If you are using **`pages`** directory then `NextSeo` is **exactly what you need - [dangerouslySetAllPagesToNoFollow](#dangerouslysetallpagestonofollow) - [robotsProps](#robotsprops) - [Twitter](#twitter) - - [facebook](#facebook) + - [Facebook](#facebook) - [Canonical URL](#canonical-url) - [Alternate](#alternate) - [Additional Meta Tags](#additional-meta-tags) @@ -111,6 +111,7 @@ If you are using **`pages`** directory then `NextSeo` is **exactly what you need - [Organization](#organization) - [Brand](#brand) - [WebPage](#webpage) + - [WebSite](#website) - [Image Metadata](#image-metadata) - [Contributors](#contributors) @@ -3474,6 +3475,48 @@ export default () => ( For reference and more info check [Docs](https://schema.org/WebPage) +### WebSite + +```jsx +import React from 'react'; +import { WebSiteJsonLd } from 'next-seo'; + +export default () => ( + <> +

WebSite

+ + +); +``` + +**Data required properties** + +| Property | Info | +| -------- | --------------- | +| `name` | 'The site name' | + +**Data Recommended properties** + +| Property | Info | +| --------------- | -------------------------------------------------------- | +| `url` | The URL of the website | +| `alternateName` | One or multiple alternate names for the site | +| `publisher` | | +| `publisher.id` | Id of the Person or Organization that published the site | + +**Other** +| `useAppDir` | This should be set to true if using the new app directory. Not required if outside of the app +directory. | + +For reference and more info check [Docs](https://schema.org/WebSite) + ### Image Metadata ```jsx diff --git a/cypress/e2e/webSiteJsonLd.spec.js b/cypress/e2e/webSiteJsonLd.spec.js new file mode 100644 index 00000000..b493fcda --- /dev/null +++ b/cypress/e2e/webSiteJsonLd.spec.js @@ -0,0 +1,57 @@ +import { assertSchema } from '@cypress/schema-tools'; +import schemas from '../schemas'; + +describe('WebPage JSON-LD', () => { + it('matches schema', () => { + cy.visit('http://localhost:3000/jsonld/webSite'); + cy.get('head script[type="application/ld+json"]').then(tags => { + const jsonLD = JSON.parse(tags[0].innerHTML); + assertSchema(schemas)('WebSite', '1.0.0')(jsonLD); + }); + }); + + it('renders with all props', () => { + cy.visit('http://localhost:3000/jsonld/webSite'); + cy.get('head script[type="application/ld+json"]').then(tags => { + const jsonLD = JSON.parse(tags[0].innerHTML); + expect(jsonLD).to.deep.equal({ + '@context': 'https://schema.org', + '@type': 'WebSite', + name: 'Example', + alternateName: ['Example Org', 'Example Organization'], + url: 'https://example.org', + publisher: { + '@id': 'https://example.org/#organization', + }, + }); + }); + }); + + it('renders without a publisher', () => { + cy.visit('http://localhost:3000/jsonld/webSite/withoutPublisher'); + cy.get('head script[type="application/ld+json"]').then(tags => { + const jsonLD = JSON.parse(tags[0].innerHTML); + expect(jsonLD).to.deep.equal({ + '@context': 'https://schema.org', + '@type': 'WebSite', + name: 'Example', + alternateName: ['Example Org', 'Example Organization'], + url: 'https://example.org', + }); + }); + }); + + it('renders with a single alternate name', () => { + cy.visit('http://localhost:3000/jsonld/webSite/withSingleAlternateName'); + cy.get('head script[type="application/ld+json"]').then(tags => { + const jsonLD = JSON.parse(tags[0].innerHTML); + expect(jsonLD).to.deep.equal({ + '@context': 'https://schema.org', + '@type': 'WebSite', + name: 'Example', + alternateName: 'Example Organization', + url: 'https://example.org', + }); + }); + }); +}); diff --git a/cypress/schemas/index.js b/cypress/schemas/index.js index c4892c35..e3397d8e 100644 --- a/cypress/schemas/index.js +++ b/cypress/schemas/index.js @@ -29,7 +29,7 @@ import howToVersions from './how-to-schema'; import imageVersions from './image-schema'; import campgroundVersions from './campground-schema'; import parkVersions from './park-schema'; - +import webSiteVersions from './web-site-schema'; const schemas = combineSchemas( articleVersions, @@ -61,5 +61,6 @@ const schemas = combineSchemas( imageVersions, campgroundVersions, parkVersions, + webSiteVersions, ); export default schemas; diff --git a/cypress/schemas/web-site-schema.js b/cypress/schemas/web-site-schema.js new file mode 100644 index 00000000..c8e84498 --- /dev/null +++ b/cypress/schemas/web-site-schema.js @@ -0,0 +1,61 @@ +import { versionSchemas } from '@cypress/schema-tools'; + +const webSite100 = { + version: { + major: 1, + minor: 0, + patch: 0, + }, + schema: { + type: 'object', + title: 'WebSite', + description: 'An example schema describing JSON-LD for type: WebSite', + properties: { + '@context': { + type: 'string', + description: 'Schema.org context', + }, + '@type': { + type: 'string', + description: 'JSON-LD type: WebSite', + }, + name: { + type: 'string', + description: 'The site name', + }, + url: { + type: 'string', + description: 'The URL of the website', + }, + alternateName: { + type: 'array', + items: { + type: 'string', + }, + description: 'One or multiple alternate names for the site', + }, + publisher: { + type: 'object', + properties: { + '@id': { + type: 'string', + description: 'Id of the publisher node', + }, + }, + }, + }, + }, + example: { + '@context': 'https://schema.org', + '@type': 'WebSite', + url: 'https://example.org', + name: 'Example', + alternateName: ['Example Org', 'Example Organization'], + publisher: { + '@id': 'https://example.org/#organization', + }, + }, +}; + +const webSiteVersions = versionSchemas(webSite100); +export default webSiteVersions; diff --git a/e2e/pages/jsonld/index.tsx b/e2e/pages/jsonld/index.tsx index 4665310d..938ce9f2 100644 --- a/e2e/pages/jsonld/index.tsx +++ b/e2e/pages/jsonld/index.tsx @@ -31,6 +31,7 @@ const allJsonLDPages = [ 'videoGame', 'webPage', 'webPage2', + 'webSite', ]; const Home = () => ( diff --git a/e2e/pages/jsonld/webSite/index.tsx b/e2e/pages/jsonld/webSite/index.tsx new file mode 100644 index 00000000..88ac8b53 --- /dev/null +++ b/e2e/pages/jsonld/webSite/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { WebSiteJsonLd } from '../../../..'; + +function WebPage() { + return ( + <> +

WebSite

+ + + ); +} + +export default WebPage; diff --git a/e2e/pages/jsonld/webSite/withSingleAlternateName.tsx b/e2e/pages/jsonld/webSite/withSingleAlternateName.tsx new file mode 100644 index 00000000..1b6dce01 --- /dev/null +++ b/e2e/pages/jsonld/webSite/withSingleAlternateName.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { WebSiteJsonLd } from '../../../..'; + +function WebPage() { + return ( + <> +

WebSite

+ + + ); +} + +export default WebPage; diff --git a/e2e/pages/jsonld/webSite/withoutPublisher.tsx b/e2e/pages/jsonld/webSite/withoutPublisher.tsx new file mode 100644 index 00000000..72508512 --- /dev/null +++ b/e2e/pages/jsonld/webSite/withoutPublisher.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { WebSiteJsonLd } from '../../../..'; + +function WebPage() { + return ( + <> +

WebSite

+ + + ); +} + +export default WebPage; diff --git a/src/index.tsx b/src/index.tsx index e5687e39..907767e5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -72,4 +72,5 @@ export { CampgroundJsonLdProps, } from './jsonld/campground'; export { default as ParkJsonLd, ParkJsonLdProps } from './jsonld/park'; +export { default as WebSiteJsonLd, WebSiteJsonLdProps } from './jsonld/webSite'; export { DefaultSeoProps, NextSeoProps } from './types'; diff --git a/src/jsonld/webSite.tsx b/src/jsonld/webSite.tsx new file mode 100644 index 00000000..5de8d6f4 --- /dev/null +++ b/src/jsonld/webSite.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +import { JsonLd, JsonLdProps } from './jsonld'; +import { WebSitePublisher } from '../types'; +import { setWebSitePublisher } from '../utils/schema/setWebSitePublisher'; + +export interface WebSiteJsonLdProps extends JsonLdProps { + name: string; + url?: string; + alternateName?: string | string[]; + publisher?: WebSitePublisher; +} + +function WebSiteJsonLd({ + type = 'WebSite', + keyOverride, + publisher, + ...rest +}: WebSiteJsonLdProps) { + const data = { + ...rest, + publisher: setWebSitePublisher(publisher), + }; + + return ( + + ); +} + +export default WebSiteJsonLd; diff --git a/src/types.ts b/src/types.ts index dbfe2ab6..c7e05b02 100644 --- a/src/types.ts +++ b/src/types.ts @@ -54,6 +54,7 @@ export type StepDetails = { export interface Person { name: string; } + export interface Answer { text: string; dateCreated?: string; @@ -79,10 +80,12 @@ export interface Instruction { url?: string; image?: string; } + export interface Performer { type?: 'Person' | 'PerformingGroup'; name: string; } + export interface Place { name: string; address: Address; @@ -123,6 +126,7 @@ export interface ContactPoint { availableLanguage?: string | string[]; contactOption?: string | string[]; } + export interface CreativeWork { author: string; about: string; @@ -143,16 +147,19 @@ export interface Question { questionName: string; acceptedAnswerText: string; } + export interface Provider { type?: 'Organization' | 'Person'; name: string; url?: string; } + export interface ItemListElements { item: string; name: string; position: number; } + export interface OpenGraphMedia { url: string; width?: number | null; @@ -395,6 +402,10 @@ export type Publisher = { name: string; }; +export type WebSitePublisher = { + id: string; +}; + export type ReviewedBy = { type?: string; name: string; @@ -511,4 +522,5 @@ export interface DefaultSeoProps { additionalLinkTags?: ReadonlyArray; children?: never; } + export interface BuildTagsParams extends DefaultSeoProps, NextSeoProps {} diff --git a/src/utils/schema/setWebSitePublisher.ts b/src/utils/schema/setWebSitePublisher.ts new file mode 100644 index 00000000..aa3ab50b --- /dev/null +++ b/src/utils/schema/setWebSitePublisher.ts @@ -0,0 +1,11 @@ +import { WebSitePublisher } from '../../types'; + +export function setWebSitePublisher(publisher?: WebSitePublisher) { + if (publisher) { + return { + '@id': publisher.id, + }; + } + + return undefined; +}