diff --git a/README.md b/README.md
index dcec3213..469e8984 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,7 @@ If you are using **`pages`** directory then `NextSeo` is **exactly what you need
- [Product](#product)
- [Social Profile](#social-profile)
- [News Article](#news-article)
+ - [Park](#park)
- [Video](#video-1)
- [VideoGame](#videogame)
- [Event](#event)
@@ -1102,6 +1103,7 @@ Below you will find a very basic page implementing each of the available JSON-LD
- [Product](#product)
- [Social Profile](#social-profile)
- [News Article](#news-article)
+- [Park](#park)
Pull request very welcome to add any from the list [found on here](https://developers.google.com/search/docs/data-types/article)
@@ -2241,6 +2243,91 @@ const Page = () => (
export default Page;
```
+### Park
+
+```jsx
+import { ParkJsonLd } from 'next-seo';
+
+const Page = () => (
+ <>
+
Park JSON-LD
+
+ >
+);
+
+export default Page;
+```
+
+**Required properties**
+
+| Property | Info |
+| ------------------------- | ------------------------------------------------------------- |
+| `@id` | Globally unique ID of the specific park in the form of a URL. |
+| `address` | Address of the specific park 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. |
+| `name` | Park name. |
+| `description` | Park description. |
+
+**Supported properties**
+
+| Property | Info |
+| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `geo` | Geographic coordinates of the park. |
+| `geo.latitude` | The latitude of the park location |
+| `geo.longitude` | The longitude of the park location |
+| `images` | An image or images of the park. Required for valid markup depending on the type |
+| `telephone` | A business phone number meant to be the primary contact method for customers. |
+| `url` | The fully-qualified URL of the specific park. |
+| `openingHours` | Opening hour specification of the park. You can provide this as a single object, or an array of objects with the properties below. |
+| `openingHours.opens` | The opening hour of the place or service on the given day(s) of the week. |
+| `openingHours.closes` | The closing hour of the place or service on the given day(s) of the week. |
+| `openingHours.dayOfWeek` | The day of the week for which these opening hours are valid. Can be a string or array of strings. Refer to [DayOfWeek](https://schema.org/DayOfWeek) |
+| `openingHours.validFrom` | The date when the item becomes valid. |
+| `openingHours.validThrough` | The date after when the item is not valid. |
+| `isAccessibleForFree` | Whether or not the park is accessible for free. |
+
+**Other**
+| `useAppDir` | This should be set to true if using new app directory. Not required if outside of app directory. |
+
[Google Docs for Social Profile](https://developers.google.com/search/docs/data-types/social-profile)
### Video
diff --git a/cypress/e2e/park.spec.js b/cypress/e2e/park.spec.js
new file mode 100644
index 00000000..64538adb
--- /dev/null
+++ b/cypress/e2e/park.spec.js
@@ -0,0 +1,61 @@
+import { assertSchema } from '@cypress/schema-tools';
+import schemas from '../schemas';
+
+describe('Park JSON-LD', () => {
+ it('matches schema', () => {
+ cy.visit('http://localhost:3000/jsonld/park');
+ cy.get('head script[type="application/ld+json"]').then(tags => {
+ const jsonLD = JSON.parse(tags[0].innerHTML);
+ console.log('jsonLD', jsonLD);
+ assertSchema(schemas)('Park', '1.0.0')(jsonLD);
+ });
+ });
+
+ it('renders with all props', () => {
+ cy.visit('http://localhost:3000/jsonld/park');
+ 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': 'Park',
+ '@id': 'https://www.example.com/park/minnewaska-state-park',
+ name: 'Minnewaska State Park',
+ description: 'Description about Minnewaska State Park',
+ url: 'https://www.example.com/park',
+ telephone: '+18452550752',
+ image: ['https://example.com/photos/1x1/photo.jpg'],
+ address: {
+ '@type': 'PostalAddress',
+ streetAddress: '5281 Route 44-55',
+ addressLocality: 'Kerhonkson',
+ addressRegion: 'NY',
+ postalCode: '12446',
+ addressCountry: 'US',
+ },
+ geo: {
+ latitude: '41.735149',
+ longitude: '-74.239037',
+ '@type': 'GeoCoordinates',
+ },
+ openingHoursSpecification: [
+ {
+ opens: '09:00',
+ closes: '18:00',
+ dayOfWeek: [
+ 'Monday',
+ 'Tuesday',
+ 'Wednesday',
+ 'Thursday',
+ 'Friday',
+ 'Saturday',
+ 'Sunday',
+ ],
+ validFrom: '2019-12-23',
+ validThrough: '2020-04-02',
+ '@type': 'OpeningHoursSpecification',
+ },
+ ],
+ });
+ });
+ });
+});
diff --git a/cypress/schemas/index.js b/cypress/schemas/index.js
index 731dd620..c4892c35 100644
--- a/cypress/schemas/index.js
+++ b/cypress/schemas/index.js
@@ -28,6 +28,8 @@ import videoVersions from './video-schema';
import howToVersions from './how-to-schema';
import imageVersions from './image-schema';
import campgroundVersions from './campground-schema';
+import parkVersions from './park-schema';
+
const schemas = combineSchemas(
articleVersions,
@@ -58,5 +60,6 @@ const schemas = combineSchemas(
howToVersions,
imageVersions,
campgroundVersions,
+ parkVersions,
);
export default schemas;
diff --git a/cypress/schemas/park-schema.js b/cypress/schemas/park-schema.js
new file mode 100644
index 00000000..233a474a
--- /dev/null
+++ b/cypress/schemas/park-schema.js
@@ -0,0 +1,164 @@
+import { versionSchemas } from '@cypress/schema-tools';
+
+import address100 from './address';
+
+const park100 = {
+ version: {
+ major: 1,
+ minor: 0,
+ patch: 0,
+ },
+ schema: {
+ type: 'object',
+ title: 'Park',
+ description: 'An example schema describing JSON-LD for type: Park',
+ properties: {
+ '@context': {
+ type: 'string',
+ description: 'Schema.org context',
+ },
+ '@type': {
+ type: 'string',
+ description:
+ 'Any more specific type supported by Park https://schema.org/Park',
+ },
+ '@id': {
+ type: 'string',
+ description:
+ 'Globally unique ID of the specific park in the form of a URL. The ID should be stable and unchanging over time. Google Search treats the URL as an opaque string and it does not have to be a working link.',
+ },
+ name: {
+ type: 'string',
+ description: 'The name of the park.',
+ },
+ description: {
+ type: 'string',
+ description: 'Description for the park.',
+ },
+ url: {
+ type: 'string',
+ description:
+ 'The fully-qualified URL of the specific park location. Unlike the @id property, this URL property should be a working link.',
+ },
+ telephone: {
+ type: 'string',
+ description:
+ 'A park phone number meant to be the primary contact method for customers. Be sure to include the country code and area code in the phone number.',
+ },
+ image: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ description: "Array of image URL's",
+ },
+ address: {
+ ...address100.schema,
+ see: address100,
+ },
+ geo: {
+ type: 'object',
+ description: "Array of social profile URL's",
+ properties: {
+ '@type': {
+ type: 'string',
+ description: 'JSON-LD type: GeoCoordinates',
+ },
+ latitude: {
+ type: 'string',
+ description: 'The latitude of the park location.',
+ },
+ longitude: {
+ type: 'string',
+ description: 'The longitude of the park location.',
+ },
+ },
+ },
+ openingHoursSpecification: {
+ type: 'array',
+ description: 'Opening hour specification for the park',
+ item: {
+ type: 'object',
+ properties: {
+ '@type': {
+ type: 'string',
+ description: 'JSON-LD type: OpeningHoursSpecification',
+ },
+ opens: {
+ type: 'string',
+ description:
+ 'The opening hour of the place or service on the given day(s) of the week.',
+ },
+ closes: {
+ type: 'string',
+ description:
+ 'The closing hour of the place or service on the given day(s) of the week.',
+ },
+ dayOfWeek: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ description:
+ 'The day of the week for which these opening hours are valid.',
+ },
+ validFrom: {
+ type: 'string',
+ description: 'The date when the item becomes valid.',
+ },
+ validThrough: {
+ type: 'string',
+ description: 'The date after when the item is not valid.',
+ },
+ },
+ },
+ },
+ },
+ required: true,
+ additionalProperties: false,
+ },
+ example: {
+ '@context': 'https://schema.org',
+ '@type': 'Park',
+ '@id': 'https://www.example.com/park/minnewaska-state-park',
+ name: 'Minnewaska State Park',
+ description: 'Description about Minnewaska State Park',
+ url: 'https://www.example.com/park',
+ telephone: '+18452550752',
+ image: ['https://example.com/photos/1x1/photo.jpg'],
+ address: {
+ '@type': 'PostalAddress',
+ streetAddress: '5281 Route 44-55',
+ addressLocality: 'Kerhonkson',
+ addressRegion: 'NY',
+ postalCode: '12446',
+ addressCountry: 'US',
+ },
+ geo: {
+ latitude: '41.735149',
+ longitude: '-74.239037',
+ '@type': 'GeoCoordinates',
+ },
+ openingHoursSpecification: [
+ {
+ opens: '09:00',
+ closes: '18:00',
+ dayOfWeek: [
+ 'Monday',
+ 'Tuesday',
+ 'Wednesday',
+ 'Thursday',
+ 'Friday',
+ 'Saturday',
+ 'Sunday',
+ ],
+ validFrom: '2019-12-23',
+ validThrough: '2020-04-02',
+ '@type': 'OpeningHoursSpecification',
+ },
+ ],
+ },
+};
+
+const park = versionSchemas(park100);
+export default park;
diff --git a/e2e/pages/jsonld/index.tsx b/e2e/pages/jsonld/index.tsx
index 9f1e46cf..4665310d 100644
--- a/e2e/pages/jsonld/index.tsx
+++ b/e2e/pages/jsonld/index.tsx
@@ -19,6 +19,7 @@ const allJsonLDPages = [
'logo',
'newsarticle',
'organization',
+ 'park',
'product',
'profilePage',
'qaPage',
diff --git a/e2e/pages/jsonld/park.tsx b/e2e/pages/jsonld/park.tsx
new file mode 100644
index 00000000..d6a99fed
--- /dev/null
+++ b/e2e/pages/jsonld/park.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { ParkJsonLd } from '../../..';
+
+function Park() {
+ return (
+ <>
+ Park
+
+ >
+ );
+}
+
+export default Park;
diff --git a/src/index.tsx b/src/index.tsx
index 2b53b27a..e5687e39 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -71,5 +71,5 @@ export {
default as CampgroundJsonLd,
CampgroundJsonLdProps,
} from './jsonld/campground';
-
+export { default as ParkJsonLd, ParkJsonLdProps } from './jsonld/park';
export { DefaultSeoProps, NextSeoProps } from './types';
diff --git a/src/jsonld/park.tsx b/src/jsonld/park.tsx
new file mode 100644
index 00000000..58e0fb7f
--- /dev/null
+++ b/src/jsonld/park.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { JsonLd, JsonLdProps } from './jsonld';
+import { Address, Geo, OpeningHoursSpecification } from 'src/types';
+import { setGeo } from 'src/utils/schema/setGeo';
+import { setAddress } from 'src/utils/schema/setAddress';
+import { setOpeningHours } from 'src/utils/schema/setOpeningHours';
+
+export interface ParkJsonLdProps extends JsonLdProps {
+ address: Address | Address[];
+ description: string;
+ geo?: Geo;
+ images?: string[];
+ isAccessibleForFree?: boolean;
+ name: string;
+ openingHours?: OpeningHoursSpecification | OpeningHoursSpecification[];
+ telephone?: string;
+ type?: string;
+ url?: string;
+}
+
+function ParkJsonLd({
+ address,
+ geo,
+ images,
+ keyOverride,
+ openingHours,
+ type = 'Park',
+ ...rest
+}: ParkJsonLdProps) {
+ const data = {
+ image: images,
+ openingHoursSpecification: Array.isArray(openingHours)
+ ? openingHours.map(setOpeningHours)
+ : setOpeningHours(openingHours),
+ address: setAddress(address),
+ geo: setGeo(geo),
+ ...rest,
+ };
+ return (
+
+ );
+}
+
+export default ParkJsonLd;