diff --git a/README.md b/README.md
index 3f9fbdc6..25b571d1 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,9 @@ looking for inspiration on what to add.
- [Movie](#movie)
- [Recipe](#recipe-1)
- [Software App](#software-app)
+ - [Organization](#organization)
+ - [Brand](#brand)
+ - [WebPage](#webpage)
- [Contributors](#contributors)
@@ -2725,6 +2728,170 @@ export default () => (
For reference and more info check [Google docs for Software App](https://developers.google.com/search/docs/data-types/software-app)
+
+### Organization
+
+```jsx
+import React from 'react';
+import { OrganizationJsonLd } from 'next-seo';
+
+export default () => (
+ <>
+
Organization JSON-LD
+
+ >
+);
+```
+
+**Data required properties**
+
+| Property | Info |
+| -------------------------- | -------------------------------------------------------------------------------------------------------- |
+| `name` | The name of the Organization. |
+| `url` | Url of the organization |
+| `contactPoint` | |
+| `contactPoint.telephone` | An internationalized version of the phone number, starting with the "+" symbol and country code |
+| `contactPoint.contactType` | Description of the purpose of the phone number i.e. `Technical Support`. |
+**Data Recommended properties**
+
+| Property | Info |
+| -------------------------------- | ---------------------------------------------------------------------------------------------------------- |
+| `logo` | ImageObject or URL an associated logo to the Organization. |
+| `organizationType` | Organization type, check [here](https://schema.org/Organization#subtypes) |
+| `legalName` | The official name of the organization, e.g. the registered company name. |
+| `sameAs` | URL of a reference Web page that unambiguously indicates the item's identity. |
+| `address` | Address of the specific business location |
+| `address.addressCountry` | The 2-letter ISO 3166-1 alpha-2 country code |
+| `address.addressLocality` | City |
+| `address.addressRegion` | State or province, if applicable. |
+| `address.postalCode` | Postal or zip code. |
+| `address.streetAddress` | Street number, street name, and unit number. |
+| `contactPoint.areaServed` | `String` or `Array` of geographical regions served by the business. Example `"US"` or `["US", "CA", "MX"]` |
+| `contactPoint.availableLanguage` | Details about the language spoken. Example `"English"` or `["English", "French"]` |
+
+For reference and more info check [Docs](https://schema.org/Organization)
+
+
+### Brand
+
+```jsx
+import React from 'react';
+import { BrandJsonLd } from 'next-seo';
+
+export default () => (
+ <>
+ Brand JSON-LD
+
+ >
+);
+```
+
+**Data required properties**
+
+| Property | Info |
+| -------------------------- | -------------------------------------------------------------------------------------------------------- |
+| `id` | 'URL to main entity of page' |
+
+**Data Recommended properties**
+
+| Property | Info |
+| ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
+| `logo` | ImageObject or URL an associated logo to the Organization. |
+| `slogan` | A slogan or motto associated with the item. |
+| `aggregateRating.ratingValue` | The rating for the content.(Check the [reference](https://schema.org/ratingValue) |
+| `aggregateRating.ratingCount` | The count of total number of ratings. |
+| `aggregateRating.reviewCount` | The count of total number of reviews. |
+| `aggregateRating.bestRating` | The highest value allowed in this rating system. If bestRating is omitted, 5 is assumed. |
+
+
+For reference and more info check [Docs](https://schema.org/Brand)
+
+
+### WebPage
+
+```jsx
+import React from 'react';
+import { WebPageJsonLd } from 'next-seo';
+
+export default () => (
+ <>
+ WebPage JSON-LD
+
+ >
+);
+```
+
+**Data required properties**
+
+| Property | Info |
+| -------------------------- | -------------------------------------------------------------------------------------------------------- |
+| `id` | 'URL to main entity of page' |
+
+**Data Recommended properties**
+
+| Property | Info |
+| ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
+| `description` | ImageObject or URL an associated logo to the Organization. |
+| `lastReviewed` | Date on which the content on this web page was last reviewed for accuracy and/or completeness. |
+| `reviewedBy.type` | People or organizations that will review the content of the web page. |
+| `reviewedBy.name` | Name of the entity that have reviewed the content on this web page for accuracy and/or completeness. |
+
+
+For reference and more info check [Docs](https://schema.org/Brand)
+
+
## Contributors
Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
diff --git a/cypress/e2e/jsonld.spec.js b/cypress/e2e/jsonld.spec.js
index f0f4ca7f..e93ab05b 100644
--- a/cypress/e2e/jsonld.spec.js
+++ b/cypress/e2e/jsonld.spec.js
@@ -1,7 +1,7 @@
import { assertSchema } from '@cypress/schema-tools';
import schemas from '../schemas';
-const expectedJSONResults = 23;
+const expectedJSONResults = 26;
const articleLdJsonIndex = 0;
const breadcrumbLdJsonIndex = 1;
@@ -26,6 +26,9 @@ const softwareAppJsonIndex = 19;
const collectionPageLdJsonIndex = 20;
const profilePageLdJsonIndex = 21;
const videoGameLdJsonIndex = 22;
+const organizationLdJsonIndex = 23;
+const brandLdJsonIndex = 24;
+const webPageLdJsonIndex = 25;
describe('Validates JSON-LD For:', () => {
it('Article', () => {
@@ -1845,4 +1848,111 @@ describe('Validates JSON-LD For:', () => {
});
});
});
+
+ it('Organization', () => {
+ cy.visit('http://localhost:3000/jsonld');
+ cy.get('head script[type="application/ld+json"]')
+ .should('have.length', expectedJSONResults)
+ .then(tags => {
+ const jsonLD = JSON.parse(tags[organizationLdJsonIndex].innerHTML);
+ assertSchema(schemas)('Organization', '1.0.0')(jsonLD);
+ });
+ });
+
+ it('Organization Matches', () => {
+ cy.visit('http://localhost:3000/jsonld');
+ cy.get('head script[type="application/ld+json"]')
+ .should('have.length', expectedJSONResults)
+ .then(tags => {
+ const jsonLD = JSON.parse(tags[organizationLdJsonIndex].innerHTML);
+ expect(jsonLD).to.deep.equal({
+ '@context': 'https://schema.org',
+ '@id': 'https://www.purpule-fox.io/#corporation',
+ '@type': 'Corporation',
+ name: 'Purple Fox',
+ legalName: 'Purple Fox LLC',
+ logo: 'https://www.example.com/photos/logo.jpg',
+ url: 'https://www.purpule-fox.io/',
+ address: {
+ '@type': 'PostalAddress',
+ streetAddress: '1600 Saratoga Ave',
+ addressLocality: 'San Jose',
+ addressRegion: 'CA',
+ postalCode: '95129',
+ addressCountry: 'US',
+ },
+ contactPoints: [
+ {
+ '@type': 'ContactPoint',
+ contactType: 'customer service',
+ telephone: '+1-877-746-0909',
+ areaServed: 'US',
+ availableLanguage: ['English', 'Spanish', 'French'],
+ contactOption: 'TollFree',
+ },
+ ],
+ sameAs: ['https://www.orange-fox.com'],
+ });
+ });
+ });
+ it('Brand', () => {
+ cy.visit('http://localhost:3000/jsonld');
+ cy.get('head script[type="application/ld+json"]')
+ .should('have.length', expectedJSONResults)
+ .then(tags => {
+ const jsonLD = JSON.parse(tags[brandLdJsonIndex].innerHTML);
+ assertSchema(schemas)('Brand', '1.0.0')(jsonLD);
+ });
+ });
+
+ it('Brand Matches', () => {
+ cy.visit('http://localhost:3000/jsonld');
+ cy.get('head script[type="application/ld+json"]')
+ .should('have.length', expectedJSONResults)
+ .then(tags => {
+ const jsonLD = JSON.parse(tags[brandLdJsonIndex].innerHTML);
+ expect(jsonLD).to.deep.equal({
+ '@context': 'https://schema.org',
+ '@type': 'Brand',
+ '@id': 'https://www.purpule-fox.io/#brand',
+ logo: 'https://www.example.com/photos/logo.jpg',
+ slogan: 'What does the fox say?',
+ aggregateRating: {
+ '@type': 'AggregateRating',
+ ratingValue: '4.4',
+ reviewCount: '89',
+ },
+ });
+ });
+ });
+
+ it('WebPage', () => {
+ cy.visit('http://localhost:3000/jsonld');
+ cy.get('head script[type="application/ld+json"]')
+ .should('have.length', expectedJSONResults)
+ .then(tags => {
+ const jsonLD = JSON.parse(tags[webPageLdJsonIndex].innerHTML);
+ assertSchema(schemas)('WebPage', '1.0.0')(jsonLD);
+ });
+ });
+
+ it('WebPage Matches', () => {
+ cy.visit('http://localhost:3000/jsonld');
+ cy.get('head script[type="application/ld+json"]')
+ .should('have.length', expectedJSONResults)
+ .then(tags => {
+ const jsonLD = JSON.parse(tags[webPageLdJsonIndex].innerHTML);
+ expect(jsonLD).to.deep.equal({
+ '@context': 'https://schema.org',
+ '@type': 'WebPage',
+ '@id': 'https://www.purpule-fox.io/#info',
+ description: 'This is a description.',
+ lastReviewed: '2021-05-26T05:59:02.085Z',
+ reviewedBy: {
+ '@type': 'Organization',
+ name: 'Garmeeh',
+ },
+ });
+ });
+ });
});
diff --git a/cypress/schemas/brand-schema.js b/cypress/schemas/brand-schema.js
new file mode 100644
index 00000000..0c55697a
--- /dev/null
+++ b/cypress/schemas/brand-schema.js
@@ -0,0 +1,53 @@
+import { versionSchemas } from '@cypress/schema-tools';
+
+import { aggregateRating100 } from './common';
+
+const brand100 = {
+ version: {
+ major: 1,
+ minor: 0,
+ patch: 0,
+ },
+ schema: {
+ type: 'object',
+ title: 'Brand',
+ description: 'Bramd description with slogan and some characteristics.',
+ properties: {
+ '@context': {
+ type: 'string',
+ description: 'Schema.org context',
+ },
+ '@type': {
+ type: 'string',
+ description: 'JSON-LD type: PostalAddress',
+ },
+ '@id': {
+ type: 'string',
+ description: 'URL to main entity of page',
+ },
+ logo: {
+ type: 'string',
+ description: "Url of the Organization's logo",
+ },
+ slogan: {
+ type: 'string',
+ description: 'Slogan of the brand',
+ },
+ aggregateRating: {
+ ...aggregateRating100.schema,
+ see: aggregateRating100,
+ },
+ },
+ },
+ example: {
+ '@context': 'https://schema.org',
+ '@type': 'Brand',
+ '@id': 'https://www.purpule-fox.io/#corporation',
+ logo: 'https://www.example.com/photos/logo.jpg',
+ slogan: 'What does the fox say?',
+ aggregateRating: aggregateRating100.example,
+ },
+};
+
+const brandVersions = versionSchemas(brand100);
+export default brandVersions;
diff --git a/cypress/schemas/contactPoint.js b/cypress/schemas/contactPoint.js
new file mode 100644
index 00000000..c91fa5fb
--- /dev/null
+++ b/cypress/schemas/contactPoint.js
@@ -0,0 +1,52 @@
+import { versionSchemas } from '@cypress/schema-tools';
+
+const contactPoint100 = {
+ version: {
+ major: 1,
+ minor: 0,
+ patch: 0,
+ },
+ schema: {
+ type: 'object',
+ description: 'Corporate Contact - ContactPoint',
+ properties: {
+ '@type': {
+ type: 'string',
+ description: 'ContactPoint',
+ },
+ telephone: {
+ type: 'string',
+ description: 'Telephone number of the company',
+ },
+ contactType: {
+ type: 'string',
+ description: 'The main usage of the phone number',
+ },
+ areaServed: {
+ type: ['string', 'array'],
+ description: 'Geographical region served',
+ },
+ availableLanguage: {
+ type: ['string', 'array'],
+ description: 'Language spoken',
+ },
+ contactOption: {
+ type: 'string',
+ description: 'Details about the number',
+ },
+ },
+ required: ['@type', 'telephone', 'contactType'],
+ additionalProperties: false,
+ },
+ example: {
+ '@type': 'ContactPoint',
+ contactType: 'customer service',
+ telephone: '+1-877-746-0909',
+ areaServed: 'US',
+ availableLanguage: ['English', 'Spanish', 'French'],
+ contactOption: 'TollFree',
+ },
+};
+
+const contactPoint = versionSchemas(contactPoint100);
+export default contactPoint;
diff --git a/cypress/schemas/corporate-contact-schema.js b/cypress/schemas/corporate-contact-schema.js
index 0c5e5b44..cc7c2b97 100644
--- a/cypress/schemas/corporate-contact-schema.js
+++ b/cypress/schemas/corporate-contact-schema.js
@@ -1,52 +1,5 @@
import { versionSchemas } from '@cypress/schema-tools';
-
-const contactPoint100 = {
- version: {
- major: 1,
- minor: 0,
- patch: 0,
- },
- schema: {
- type: 'object',
- description: 'Corporate Contact - ContactPoint',
- properties: {
- '@type': {
- type: 'string',
- description: 'ContactPoint',
- },
- telephone: {
- type: 'string',
- description: 'Telephone number of the company',
- },
- contactType: {
- type: 'string',
- description: 'The main usage of the phone number',
- },
- areaServed: {
- type: ['string', 'array'],
- description: 'Geographical region served',
- },
- availableLanguage: {
- type: ['string', 'array'],
- description: 'Language spoken',
- },
- contactOption: {
- type: 'string',
- description: 'Details about the number',
- },
- },
- required: ['@type', 'telephone', 'contactType'],
- additionalProperties: false,
- },
- example: {
- '@type': 'ContactPoint',
- contactType: 'customer service',
- telephone: '+1-877-746-0909',
- areaServed: 'US',
- availableLanguage: ['English', 'Spanish', 'French'],
- contactOption: 'TollFree',
- },
-};
+import contactPoint100 from './contactPoint';
const corporateContact100 = {
version: {
diff --git a/cypress/schemas/index.js b/cypress/schemas/index.js
index e29060b5..6250d244 100644
--- a/cypress/schemas/index.js
+++ b/cypress/schemas/index.js
@@ -21,6 +21,9 @@ import softwareAppVersions from './software-app-schema';
import collectionPageVersions from './collection-page-schema';
import profilePageVersions from './profile-page-schema';
import videoGameVersions from './videogame-schema';
+import organizationVersions from './organization-schema';
+import brandVersions from './brand-schema';
+import webPageVersions from './web-page-schema';
const schemas = combineSchemas(
articleVersions,
@@ -44,5 +47,8 @@ const schemas = combineSchemas(
collectionPageVersions,
profilePageVersions,
videoGameVersions,
+ organizationVersions,
+ brandVersions,
+ webPageVersions,
);
export default schemas;
diff --git a/cypress/schemas/organization-schema.js b/cypress/schemas/organization-schema.js
new file mode 100644
index 00000000..d4ad5d45
--- /dev/null
+++ b/cypress/schemas/organization-schema.js
@@ -0,0 +1,83 @@
+import { versionSchemas } from '@cypress/schema-tools';
+import address100 from './address';
+import contactPoint100 from './contactPoint';
+
+const organization100 = {
+ version: {
+ major: 1,
+ minor: 0,
+ patch: 0,
+ },
+ schema: {
+ type: 'object',
+ title: 'Organization',
+ description: 'An example schema describing JSON-LD for type: Organization',
+ properties: {
+ '@context': {
+ type: 'string',
+ description: 'Schema.org context',
+ },
+ '@type': {
+ type: 'string',
+ description: 'Organization and the subtypes',
+ },
+ '@id': {
+ type: 'string',
+ description: 'URL to main entity of page',
+ },
+ logo: {
+ type: 'string',
+ description: "Url of the Organization's logo",
+ },
+ legalName: {
+ type: 'string',
+ description: 'Legal name of the organization, e.g Purple Fox SA or LLC',
+ },
+ name: {
+ type: 'string',
+ description: 'Name of the organization, e.g Purple Fox',
+ },
+ address: {
+ ...address100.schema,
+ see: address100,
+ },
+ sameAs: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ description:
+ "Array of Organization's URL's, usually social urls like instagram, facebook etc.",
+ },
+ contactPoints: {
+ type: 'array',
+ items: {
+ ...contactPoint100.schema,
+ see: contactPoint100,
+ },
+ description: 'Array with contact points objects.',
+ },
+ url: {
+ type: 'string',
+ description: 'URL to main entity of page',
+ },
+ },
+ required: ['name', 'url'],
+ additionalProperties: false,
+ },
+ example: {
+ '@context': 'https://schema.org',
+ '@type': 'Corporation',
+ '@id': 'https://www.purpule-fox.io/#corporation',
+ logo: 'https://www.example.com/photos/logo.jpg',
+ legalName: 'Purple Fox LLC',
+ name: 'Purple Fox',
+ address: address100.example,
+ contactPoints: [contactPoint100.example],
+ sameAs: ['https://www.orange-fox.com'],
+ url: 'https://www.purpule-fox.io/',
+ },
+};
+
+const organizationVersions = versionSchemas(organization100);
+export default organizationVersions;
diff --git a/cypress/schemas/web-page-schema.js b/cypress/schemas/web-page-schema.js
new file mode 100644
index 00000000..c4d932cc
--- /dev/null
+++ b/cypress/schemas/web-page-schema.js
@@ -0,0 +1,63 @@
+import { versionSchemas } from '@cypress/schema-tools';
+
+const webPage100 = {
+ version: {
+ major: 1,
+ minor: 0,
+ patch: 0,
+ },
+ schema: {
+ type: 'object',
+ title: 'WebPage',
+ description: 'WebPage description with slogan and some characteristics.',
+ properties: {
+ '@context': {
+ type: 'string',
+ description: 'Schema.org context',
+ },
+ '@type': {
+ type: 'string',
+ description: 'JSON-LD type: PostalAddress',
+ },
+ '@id': {
+ type: 'string',
+ description: 'URL to main entity of page',
+ },
+ description: {
+ type: 'string',
+ description: 'Main description of the web page',
+ },
+ lastReviewed: {
+ type: 'string',
+ description:
+ 'Date on which the content on this web page was last reviewed for accuracy and/or completeness.',
+ },
+ reviewedBy: {
+ type: 'object',
+ properties: {
+ '@type': {
+ type: 'string',
+ description: 'A person or organization can review the page.',
+ },
+ name: {
+ type: 'string',
+ description: 'Name of the person or organization.',
+ },
+ },
+ },
+ },
+ },
+ example: {
+ '@context': 'https://schema.org',
+ '@type': 'WebPage',
+ '@id': 'https://www.purpule-fox.io/#corporation',
+ logo: 'https://www.example.com/photos/logo.jpg',
+ lastReviewed: '2021-05-26T05:59:02.085Z',
+ reviewedBy: {
+ name: 'Garmeeh',
+ },
+ },
+};
+
+const webPageVersions = versionSchemas(webPage100);
+export default webPageVersions;
diff --git a/e2e/pages/jsonld.tsx b/e2e/pages/jsonld.tsx
index 941f02a9..5c2698bc 100644
--- a/e2e/pages/jsonld.tsx
+++ b/e2e/pages/jsonld.tsx
@@ -21,6 +21,9 @@ import {
ProfilePageJsonLd,
CollectionPageJsonLd,
VideoGameJsonLd,
+ OrganizationJsonLd,
+ BrandJsonLd,
+ WebPageJsonLd,
} from '../..';
import Links from '../components/links';
@@ -717,6 +720,49 @@ const JsonLD = () => (
]}
/>
+
+
+
+
>
);
diff --git a/src/index.tsx b/src/index.tsx
index 4ab96e7e..a84d01df 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -59,5 +59,11 @@ export {
default as ProfilePageJsonLd,
ProfilePageJsonLdProps,
} from './jsonld/profilePage';
+export {
+ default as OrganizationJsonLd,
+ OrganizationJsonLdProps,
+} from './jsonld/organization';
+export { default as WebPageJsonLd, WebPageJsonLdProps } from './jsonld/webPage';
+export { default as BrandJsonLd, BrandJsonLdProps } from './jsonld/brand';
export { DefaultSeoProps, NextSeoProps } from './types';
diff --git a/src/jsonld/brand.tsx b/src/jsonld/brand.tsx
new file mode 100644
index 00000000..1914e0ab
--- /dev/null
+++ b/src/jsonld/brand.tsx
@@ -0,0 +1,42 @@
+import React, { FC } from 'react';
+import Head from 'next/head';
+
+import markup from '../utils/markup';
+
+import { AggregateRating } from '../types';
+import { buildAggregateRating } from '../utils/buildAggregateRating';
+
+export interface BrandJsonLdProps {
+ id: string;
+ slogan?: string;
+ logo?: string;
+ aggregateRating?: AggregateRating;
+}
+
+const BrandJsonLd: FC = ({
+ id,
+ slogan,
+ logo,
+ aggregateRating,
+}) => {
+ const jslonld = `{
+ "@context": "https://schema.org",
+ "@type": "Brand",
+ ${aggregateRating ? buildAggregateRating(aggregateRating) : ''}
+ ${slogan ? `"slogan": "${slogan}",` : ''}
+ ${logo ? `"logo": "${logo}",` : ''}
+ "@id": "${id}"
+ }`;
+
+ return (
+
+
+
+ );
+};
+
+export default BrandJsonLd;
diff --git a/src/jsonld/corporateContact.tsx b/src/jsonld/corporateContact.tsx
index 3966fff2..b2610d84 100644
--- a/src/jsonld/corporateContact.tsx
+++ b/src/jsonld/corporateContact.tsx
@@ -19,29 +19,31 @@ export interface CorporateContactJsonLdProps {
const formatIfArray = (value: string[] | string) =>
Array.isArray(value) ? `[${value.map(val => `"${val}"`)}]` : `"${value}"`;
-const buildContactPoint = (contactPoint: ContactPoint[]) =>
- contactPoint.map(
- contact => `{
+export const buildContactPoint = (contactPoint: ContactPoint[]) =>
+ contactPoint
+ .map(
+ contact => `{
"@type": "ContactPoint",
"telephone": "${contact.telephone}",
"contactType": "${contact.contactType}"${
- contact.areaServed
- ? `,
+ contact.areaServed
+ ? `,
"areaServed": ${formatIfArray(contact.areaServed)}`
- : ''
- }${
- contact.availableLanguage
- ? `,
+ : ''
+ }${
+ contact.availableLanguage
+ ? `,
"availableLanguage": ${formatIfArray(contact.availableLanguage)}`
- : ''
- }${
- contact.contactOption
- ? `,
+ : ''
+ }${
+ contact.contactOption
+ ? `,
"contactOption": "${contact.contactOption}"`
- : ''
- }
+ : ''
+ }
}`,
- );
+ )
+ .join(',');
const CorporateContactJsonLd: FC = ({
keyOverride,
diff --git a/src/jsonld/organization.tsx b/src/jsonld/organization.tsx
new file mode 100644
index 00000000..db32f8fb
--- /dev/null
+++ b/src/jsonld/organization.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import Head from 'next/head';
+
+import markup from '../utils/markup';
+import { Address, OrganizationCategory } from '../types';
+import { buildContactPoint, ContactPoint } from './corporateContact';
+import buildAddress from '../utils/buildAddress';
+
+export interface OrganizationJsonLdProps {
+ organizationType?: OrganizationCategory;
+ id?: string;
+ name: string;
+ logo?: string;
+ url: string;
+ legalName?: string;
+ sameAs?: string[];
+ address?: Address;
+ contactPoints?: ContactPoint[];
+}
+
+const OrganizationJsonLd: React.FC = ({
+ organizationType = 'Organization',
+ id,
+ name,
+ logo,
+ url,
+ legalName,
+ sameAs = [],
+ address,
+ contactPoints = [],
+}) => {
+ const jslonld = `{
+ "@context": "https://schema.org",
+ "@type": "${organizationType}",
+ ${id ? `"@id": "${id}",` : ''}
+ ${logo ? `"logo": "${logo}",` : ''}
+ ${legalName ? `"legalName": "${legalName}",` : ''}
+ "name": "${name}",
+ ${address ? buildAddress(address) : ''}
+ ${
+ sameAs.length > 0
+ ? `"sameAs": [${sameAs.map(alias => `"${alias}"`).join(',')}],`
+ : ''
+ }
+ ${
+ contactPoints.length > 0
+ ? `"contactPoints": [${buildContactPoint(contactPoints)}],`
+ : ''
+ }
+ "url": "${url}"
+ }`;
+
+ return (
+
+
+
+ );
+};
+
+export default OrganizationJsonLd;
diff --git a/src/jsonld/webPage.tsx b/src/jsonld/webPage.tsx
new file mode 100644
index 00000000..aab1a39c
--- /dev/null
+++ b/src/jsonld/webPage.tsx
@@ -0,0 +1,49 @@
+import React, { FC } from 'react';
+import Head from 'next/head';
+
+import markup from '../utils/markup';
+
+export interface WebPageJsonLdProps {
+ id: string;
+ description?: string;
+ lastReviewed?: string;
+ reviewedBy?: {
+ type?: string;
+ name: string;
+ };
+}
+
+const WebPageJsonLd: FC = ({
+ id,
+ description,
+ lastReviewed,
+ reviewedBy,
+}) => {
+ const jslonld = `{
+ "@context": "https://schema.org",
+ "@type": "WebPage",
+ ${description ? `"description": "${description}",` : ''}
+ ${lastReviewed ? `"lastReviewed": "${lastReviewed}",` : ''}
+ ${
+ reviewedBy
+ ? `"reviewedBy": {
+ "@type": "${reviewedBy.type || 'Organization'}",
+ "name": "${reviewedBy.name}"
+ },`
+ : ''
+ }
+ "@id": "${id}"
+ }`;
+
+ return (
+
+
+
+ );
+};
+
+export default WebPageJsonLd;
diff --git a/src/types.ts b/src/types.ts
index edde3f07..ca7f07f6 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -234,6 +234,25 @@ export type ApplicationCategory =
| 'Utilities'
| 'Reference';
+export type OrganizationCategory =
+ | 'Airline'
+ | 'Consortium'
+ | 'Corporation'
+ | 'EducationalOrganization'
+ | 'FundingScheme'
+ | 'GovernmentOrganization'
+ | 'LibrarySystem'
+ | 'LocalBusiness'
+ | 'MedicalOrganization'
+ | 'NGO'
+ | 'NewsMediaOrganization'
+ | 'PerformingGroup'
+ | 'Project'
+ | 'ResearchOrganization'
+ | 'SportsOrganization'
+ | 'WorkersUnion'
+ | 'Organization';
+
export interface AdditionalRobotsProps {
nosnippet?: boolean;
maxSnippet?: number;