Repository: garmeeh/next-seo
Pull request #89 (2019-08-02 18:44:57)

Fix display of jsx tags in readme
These get interpreted as HTML tags by the markdown renderer.


Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 3591297..2567abc 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -83,6 +83,17 @@
         "doc",
         "test"
       ]
+    },
+    {
+      "login": "bolonio",
+      "name": "Adrián Bolonio",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/1288407?v=4",
+      "profile": "https://github.com/bolonio",
+      "contributions": [
+        "code",
+        "doc",
+        "test"
+      ]
     }
   ],
   "contributorsPerLine": 7
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6915e5..46ede36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 All notable changes to this project will be documented in this file.
 
+## v2.1.0 - 10/09/19
+
+### Added
+
+- Added `mobileAlternate` and `languageAlternate` [PR #90](https://github.com/garmeeh/next-seo/pull/90)
+
 ## v2.0.0 - 19/07/2019
 
 Version of 2 of next-seo released!
diff --git a/README.md b/README.md
index 5ec41e9..a477021 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Next SEO
 
-[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors)
+[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors)
 
 Next SEO is a plug in that makes managing your SEO easier in Next.js projects.
 
@@ -21,13 +21,13 @@ Version One docs can be found [here](https://github.com/garmeeh/next-seo/tree/su
   - [Add SEO to Page](#add-seo-to-page)
   - [Default SEO Configuration](#default-seo-configuration)
   - [NextSeo Options](#nextseo-options)
-  - [Additional DefaultSEO Options](#additional-defaultseo-options)
     - [Title Template](#title-template)
     - [No Index](#no-index)
     - [dangerouslySetAllPagesToNoIndex](#dangerouslysetallpagestonoindex)
     - [Twitter](#twitter)
     - [facebook](#facebook)
     - [Canonical URL](#canonical-url)
+    - [Alternate](#alternate)
     - [Additional Meta Tags](#additional-meta-tags)
 - [Open Graph](#open-graph)
   - [Open Graph Examples](#open-graph-examples)
@@ -147,7 +147,7 @@ Some tools may report this an error. See [Issue #14](https://github.com/garmeeh/
 
 `NextSeo` enables you to set some default SEO that will appear on all pages without needing to do include anything on them. You can also override these on a page by page basis if needed.
 
-To achieve this, you will need to create a custom <App>. In your pages directory create a new file, `_app.js`. See the Next.js docs [here](https://github.com/zeit/next.js/#custom-app) for more info on a custom <App>.
+To achieve this, you will need to create a custom `<App>`. In your pages directory create a new file, `_app.js`. See the Next.js docs [here](https://github.com/zeit/next.js/#custom-app) for more info on a custom `<App>`.
 
 Within this file you will need to import `DefaultSeo` from `next-seo` and pass it props.
 
@@ -206,6 +206,10 @@ From now on all of your pages will have the defaults above applied applied.
 | `noindex`                          | boolean (default false) | Sets whether page should be indexed or not [More Info](#no-index)                                                                                                                    |
 | `description`                      | string                  | Set the page meta description                                                                                                                                                        |
 | `canonical`                        | string                  | Set the page canonical url                                                                                                                                                           |
+| `mobileAlternate.media`            | string                  | Set what screen size the mobile website should be served from                                                                                                                        |
+| `mobileAlternate.href`             | string                  | Set the mobile page alternate url                                                                                                                                                    |
+| `languageAlternate.hrefLang`       | string                  | Set the language of the alternate url                                                                                                                                                |
+| `languageAlternate.href`           | string                  | Set the alternate language page url                                                                                                                                                  |
 | `additionalMetaTags`               | array                   | Allows you to add a meta tag that is not documented here. [More Info](#additional-meta-tags)                                                                                         |
 | `twitter.cardType`                 | string                  | The card type, which will be one of `summary`, `summary_large_image`, `app`, or `player`                                                                                             |
 | `twitter.site`                     | string                  | @username for the website used in the card footer                                                                                                                                    |
@@ -234,18 +238,6 @@ From now on all of your pages will have the defaults above applied applied.
 | `openGraph.article.section`        | string                  | A high-level section name. E.g. Technology                                                                                                                                           |
 | `openGraph.article.tags`           | string[]                | Tag words associated with this article.                                                                                                                                              |
 
-### Additional DefaultSEO Options
-
-Same as above along with the following.
-
-| Property                                | Type    | Description                                                                                                                                                                                                                                                                             |
-| --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `dangerouslySetAllPagesToNoIndex`       | boolean | Please see [here for more info](#dangerouslySetAllPagesToNoIndex).                                                                                                                                                                                                                      |
-| `openGraph.defaultOpenGraphImageHeight` | number  | Set's the default height for all open graph images. You should only set this if all image heights are known. It can be overrode at a page level but you must know the height to do so. Otherwise the default height will be set for a dynamic image that you do not know the height of. |
-| `openGraph.defaultOpenGraphImageWidth`  | number  | Set's the default width for all open graph images. You should only set this if all image widths are known. It can be overrode at a page level but you must know the width to do so. Otherwise the default width will be set for a dynamic image that you do not know the width of.      |
-| `openGraph.defaultOpenGraphVideoHeight` | number  | Set's the default height for all open graph videos. You should only set this if all image heights are known. It can be overrode at a page level but you must know the height to do so. Otherwise the default height will be set for a dynamic image that you do not know the height of. |
-| `openGraph.defaultOpenGraphVideoWidth`  | number  | Set's the default width for all open graph videos. You should only set this if all image widths are known. It can be overrode at a page level but you must know the width to do so. Otherwise the default width will be set for a dynamic image that you do not know the width of.      |
-
 #### Title Template
 
 Replaces `%s` with your title string
@@ -288,7 +280,7 @@ export default () => (
 
 It has the prefix of `dangerously` because it will `noindex` all pages. As this is an SEO plugin, that is kinda of a dangerous action. It is **not** bad to use this, just please be sure you want to `noindex` **EVERY** page. You can still override this at a page level if you have a use case to `index` a page. This can be done by setting `noindex: false`.
 
-The only way to unset this, is by removing the prop from the `DefaultSeo` in your custom <App>.
+The only way to unset this, is by removing the prop from the `DefaultSeo` in your custom `<App>`.
 
 #### Twitter
 
@@ -314,6 +306,26 @@ Add this on page per page basis when you want to consolidate duplicate URLs.
 canonical = 'https://www.canonical.ie/';
 ```
 
+#### Alternate
+
+This link relation is used to indicate a relation between a desktop and a mobile website to search engines.
+
+Example:
+
+```js
+mobileAlternate: {
+  media: 'only screen and (max-width: 640px)',
+  href: 'https://m.canonical.ie',
+},
+```
+
+```js
+languageAlternate: {
+  hrefLang: 'de-AT',
+  href: 'https://www.canonical.ie/de',
+},
+```
+
 #### Additional Meta Tags
 
 This allows you to add any other meta tags that are not covered in the `config`.
@@ -1122,7 +1134,20 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
 
 <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
 <!-- prettier-ignore -->
-<table><tr><td align="center"><a href="https://www.garymeehan.ie/"><img src="https://avatars1.githubusercontent.com/u/13333582?v=4" width="100px;" alt="Gary Meehan"/><br /><sub><b>Gary Meehan</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=garmeeh" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=garmeeh" title="Documentation">📖</a> <a href="#example-garmeeh" title="Examples">💡</a> <a href="https://github.com/garmeeh/next-seo/commits?author=garmeeh" title="Tests">⚠️</a></td><td align="center"><a href="https://www.jeromefitzgerald.com/"><img src="https://avatars3.githubusercontent.com/u/3099369?v=4" width="100px;" alt="Jerome Fitzgerald"/><br /><sub><b>Jerome Fitzgerald</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=JeromeFitz" title="Code">💻</a></td><td align="center"><a href="https://github.com/erickeno"><img src="https://avatars0.githubusercontent.com/u/3820632?v=4" width="100px;" alt="erick B"/><br /><sub><b>erick B</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=erickeno" title="Code">💻</a></td><td align="center"><a href="https://www.erikcondie.com"><img src="https://avatars2.githubusercontent.com/u/15269328?v=4" width="100px;" alt="Erik Condie"/><br /><sub><b>Erik Condie</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=econdie" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=econdie" title="Tests">⚠️</a> <a href="#example-econdie" title="Examples">💡</a> <a href="#ideas-econdie" title="Ideas, Planning, & Feedback">🤔</a></td><td align="center"><a href="http://timothyreynolds.co.uk"><img src="https://avatars1.githubusercontent.com/u/168870?v=4" width="100px;" alt="Tim Reynolds"/><br /><sub><b>Tim Reynolds</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=timReynolds" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=timReynolds" title="Tests">⚠️</a> <a href="#example-timReynolds" title="Examples">💡</a> <a href="https://github.com/garmeeh/next-seo/commits?author=timReynolds" title="Documentation">📖</a></td><td align="center"><a href="https://github.com/Ktchan825"><img src="https://avatars3.githubusercontent.com/u/20606631?v=4" width="100px;" alt="Ktchan825"/><br /><sub><b>Ktchan825</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=Ktchan825" title="Tests">⚠️</a> <a href="https://github.com/garmeeh/next-seo/commits?author=Ktchan825" title="Code">💻</a></td><td align="center"><a href="https://github.com/ctxquentin"><img src="https://avatars1.githubusercontent.com/u/36331776?v=4" width="100px;" alt="ctxquentin"/><br /><sub><b>ctxquentin</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=ctxquentin" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=ctxquentin" title="Documentation">📖</a> <a href="https://github.com/garmeeh/next-seo/commits?author=ctxquentin" title="Tests">⚠️</a></td></tr></table>
+<table>
+  <tr>
+    <td align="center"><a href="https://www.garymeehan.ie/"><img src="https://avatars1.githubusercontent.com/u/13333582?v=4" width="100px;" alt="Gary Meehan"/><br /><sub><b>Gary Meehan</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=garmeeh" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=garmeeh" title="Documentation">📖</a> <a href="#example-garmeeh" title="Examples">💡</a> <a href="https://github.com/garmeeh/next-seo/commits?author=garmeeh" title="Tests">⚠️</a></td>
+    <td align="center"><a href="https://www.jeromefitzgerald.com/"><img src="https://avatars3.githubusercontent.com/u/3099369?v=4" width="100px;" alt="Jerome Fitzgerald"/><br /><sub><b>Jerome Fitzgerald</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=JeromeFitz" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/erickeno"><img src="https://avatars0.githubusercontent.com/u/3820632?v=4" width="100px;" alt="erick B"/><br /><sub><b>erick B</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=erickeno" title="Code">💻</a></td>
+    <td align="center"><a href="https://www.erikcondie.com"><img src="https://avatars2.githubusercontent.com/u/15269328?v=4" width="100px;" alt="Erik Condie"/><br /><sub><b>Erik Condie</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=econdie" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=econdie" title="Tests">⚠️</a> <a href="#example-econdie" title="Examples">💡</a> <a href="#ideas-econdie" title="Ideas, Planning, & Feedback">🤔</a></td>
+    <td align="center"><a href="http://timothyreynolds.co.uk"><img src="https://avatars1.githubusercontent.com/u/168870?v=4" width="100px;" alt="Tim Reynolds"/><br /><sub><b>Tim Reynolds</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=timReynolds" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=timReynolds" title="Tests">⚠️</a> <a href="#example-timReynolds" title="Examples">💡</a> <a href="https://github.com/garmeeh/next-seo/commits?author=timReynolds" title="Documentation">📖</a></td>
+    <td align="center"><a href="https://github.com/Ktchan825"><img src="https://avatars3.githubusercontent.com/u/20606631?v=4" width="100px;" alt="Ktchan825"/><br /><sub><b>Ktchan825</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=Ktchan825" title="Tests">⚠️</a> <a href="https://github.com/garmeeh/next-seo/commits?author=Ktchan825" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/ctxquentin"><img src="https://avatars1.githubusercontent.com/u/36331776?v=4" width="100px;" alt="ctxquentin"/><br /><sub><b>ctxquentin</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=ctxquentin" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=ctxquentin" title="Documentation">📖</a> <a href="https://github.com/garmeeh/next-seo/commits?author=ctxquentin" title="Tests">⚠️</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/bolonio"><img src="https://avatars0.githubusercontent.com/u/1288407?v=4" width="100px;" alt="Adrián Bolonio"/><br /><sub><b>Adrián Bolonio</b></sub></a><br /><a href="https://github.com/garmeeh/next-seo/commits?author=bolonio" title="Code">💻</a> <a href="https://github.com/garmeeh/next-seo/commits?author=bolonio" title="Documentation">📖</a> <a href="https://github.com/garmeeh/next-seo/commits?author=bolonio" title="Tests">⚠️</a></td>
+  </tr>
+</table>
 
 <!-- ALL-CONTRIBUTORS-LIST:END -->
 
diff --git a/package.json b/package.json
index 6129948..fe3947d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "next-seo",
-  "version": "2.0.0",
+  "version": "2.1.0",
   "description": "SEO plugin for Next.js projects",
   "main": "lib/index.js",
   "files": [
diff --git a/src/meta/buildTags.tsx b/src/meta/buildTags.tsx
index 44976f4..5214607 100644
--- a/src/meta/buildTags.tsx
+++ b/src/meta/buildTags.tsx
@@ -66,6 +66,28 @@ const buildTags = (config: BuildTagsParams) => {
     );
   }
 
+  if (config.mobileAlternate) {
+    tagsToRender.push(
+      <link
+        rel="alternate"
+        key="mobileAlternate"
+        media={config.mobileAlternate.media}
+        href={config.mobileAlternate.href}
+      />,
+    );
+  }
+
+  if (config.languageAlternate) {
+    tagsToRender.push(
+      <link
+        rel="alternate"
+        key="languageAlternate"
+        hrefLang={config.languageAlternate.hrefLang}
+        href={config.languageAlternate.href}
+      />,
+    );
+  }
+
   if (config.twitter) {
     if (config.twitter.cardType) {
       tagsToRender.push(
diff --git a/src/meta/defaultSEO.tsx b/src/meta/defaultSEO.tsx
index 3273a57..c181cf9 100644
--- a/src/meta/defaultSEO.tsx
+++ b/src/meta/defaultSEO.tsx
@@ -20,6 +20,8 @@ export default class extends Component<DefaultSeoProps, {}> {
       defaultOpenGraphImageHeight,
       defaultOpenGraphVideoWidth,
       defaultOpenGraphVideoHeight,
+      mobileAlternate,
+      languageAlternate,
     } = this.props;
 
     return (
@@ -38,6 +40,8 @@ export default class extends Component<DefaultSeoProps, {}> {
           defaultOpenGraphImageHeight,
           defaultOpenGraphVideoWidth,
           defaultOpenGraphVideoHeight,
+          mobileAlternate,
+          languageAlternate,
         })}
       </Head>
     );
diff --git a/src/types.ts b/src/types.ts
index 1ede79d..644ac0e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -74,6 +74,16 @@ export interface Twitter {
   cardType?: string;
 }
 
+interface MobileAlternate {
+  media: string;
+  href: string;
+}
+
+interface LanguageAlternate {
+  hrefLang: string;
+  href: string;
+}
+
 export interface BaseMetaTag {
   content: string;
 }
@@ -96,6 +106,8 @@ export interface NextSeoProps {
   noindex?: boolean;
   description?: string;
   canonical?: string;
+  mobileAlternate?: MobileAlternate;
+  languageAlternate?: LanguageAlternate;
   openGraph?: OpenGraph;
   facebook?: { appId: string };
   twitter?: Twitter;


Test patch:
diff --git a/e2e/next-seo.config.ts b/e2e/next-seo.config.ts
index 7887311..efd8b4c 100644
--- a/e2e/next-seo.config.ts
+++ b/e2e/next-seo.config.ts
@@ -7,6 +7,14 @@ const APP_DEFAULT_SEO: DefaultSeoProps = {
   canonical: 'https://www.canonical.ie/a',
   defaultOpenGraphImageHeight: 1200,
   defaultOpenGraphImageWidth: 1200,
+  mobileAlternate: {
+    media: 'only screen and (max-width: 640px)',
+    href: 'https://m.canonical.ie',
+  },
+  languageAlternate: {
+    hrefLang: 'de-AT',
+    href: 'https://www.canonical.ie/de',
+  },
   openGraph: {
     type: 'website',
     locale: 'en_IE',
diff --git a/src/meta/__tests__/__snapshots__/buildTags.spec.tsx.snap b/src/meta/__tests__/__snapshots__/buildTags.spec.tsx.snap
index dd9e7a8..d3606e4 100644
--- a/src/meta/__tests__/__snapshots__/buildTags.spec.tsx.snap
+++ b/src/meta/__tests__/__snapshots__/buildTags.spec.tsx.snap
@@ -473,6 +473,16 @@ exports[`renders correctly 1`] = `
     content="This is a test description."
     name="description"
   />
+  <link
+    href="https://m.canonical.ie"
+    media="only screen and (max-width: 640px)"
+    rel="alternate"
+  />
+  <link
+    href="https://www.canonical.ie/de"
+    hreflang="de-AT"
+    rel="alternate"
+  />
   <meta
     content="summary_large_image"
     name="twitter:card"
diff --git a/src/meta/__tests__/buildTags.spec.tsx b/src/meta/__tests__/buildTags.spec.tsx
index a8e856a..5be9f92 100644
--- a/src/meta/__tests__/buildTags.spec.tsx
+++ b/src/meta/__tests__/buildTags.spec.tsx
@@ -12,6 +12,14 @@ const SEO: BuildTagsParams = {
   canonical: 'https://www.canonical.ie',
   defaultOpenGraphImageHeight: 1200,
   defaultOpenGraphImageWidth: 1200,
+  mobileAlternate: {
+    media: 'only screen and (max-width: 640px)',
+    href: 'https://m.canonical.ie',
+  },
+  languageAlternate: {
+    hrefLang: 'de-AT',
+    href: 'https://www.canonical.ie/de',
+  },
   openGraph: {
     type: 'website',
     locale: 'en_IE',
@@ -139,6 +147,34 @@ it('returns full array for default seo object', () => {
   const ogSiteNameTag = tags.filter(item => item.key === 'og:site_name');
   const canonicalTag = tags.filter(item => item.key === 'canonical');
 
+  const mobileAlternateTag = container.querySelectorAll(
+    'link[rel="alternate"][media]',
+  );
+  const mobileAlternateHref = container.querySelectorAll(
+    `link[href="${SEO.mobileAlternate.href}"]`,
+  );
+  const mobileAlternateMedia = container.querySelectorAll(
+    `link[media="${SEO.mobileAlternate.media}"]`,
+  );
+
+  const languageAlternateTag = container.querySelectorAll(
+    'link[rel="alternate"][hrefLang]',
+  );
+  const languageAlternateHref = container.querySelectorAll(
+    `link[href="${SEO.languageAlternate.href}"]`,
+  );
+  const languageAlternateHrefLang = container.querySelectorAll(
+    `link[hrefLang="${SEO.languageAlternate.hrefLang}"]`,
+  );
+
+  expect(Array.from(mobileAlternateTag).length).toBe(1);
+  expect(Array.from(mobileAlternateHref).length).toBe(1);
+  expect(Array.from(mobileAlternateMedia).length).toBe(1);
+
+  expect(Array.from(languageAlternateTag).length).toBe(1);
+  expect(Array.from(languageAlternateHref).length).toBe(1);
+  expect(Array.from(languageAlternateHrefLang).length).toBe(1);
+
   expect(title).toBeDefined();
   expect(Array.from(index).length).toBe(2);
   expect(Array.from(description).length).toBe(1);