Skip to content

Commit

Permalink
Adds new <Metadata> component for proper <meta> tag generation (#9315)
Browse files Browse the repository at this point in the history
Rather than trying to only allow certain props and try to transform one
to another, we're going to allow you to basically enter anything you
want, but with a couple of conventions.

I propose keeping the existing `<MetaTags>` functionality as it exists
and just add `<Metadata>` as a whole new concept. This allows us to
release sooner than 7.0, since it would be non-breaking, and makes it
easier for people to upgrade over time rather than all at once: having
to change potentially hundreds of existing `<MetaTags>` declarations
(across pages and cells) before they can deploy their app again would
not be fun. I'll make it clear in the docs that they really want to move
to `<Metadata>` once SSR is available in order to get maximum benefit
for bots crawling their site.

### Definitions

* **Top level prop** means a plain text name like "title" or
"description"
* **Namespaced prop** means a name with one or more colons, like
`og:image`

### Rules

#### Any "top level" prop will create a `<meta>` tag with `name` and
`content` attributes

```jsx
<Metadata description="Lorem ipsum dolar sit amet..." />
// generates
<meta name="description" content="Lorem ipsum dolar sit amet..." />
```

#### You can add child elements which will be appended to any you create
as props

```jsx
<Metadata description="Lorem ipsum dolar sit amet...">
  <meta httpEquiv="refresh" content="30" />
</Metadata>
// generates
<meta name="description" content="Lorem ipsum dolar sit amet..." />
<meta http-equiv="refresh" content="30" />
```

#### Any "namespaced" props will create a `<meta>` tag with `property`
and `content` attributes

```jsx
<Metadata og={{ image:"http://host.test/image.jpg" />
// generates
<meta property="og:image" content="http://host.test/image.jpg" />
```

#### You can create multiple `<meta>` tags with the same name/property,
the OpenGraph spec encourages this

```jsx
<Metadata og={{ image: ["http://host.test/image1.jpg", "http://host.test/image2.jpg"] />
// generates
<meta property="og:image" content="http://host.test/image1.jpg" />
<meta property="og:image" content="http://host.test/image2.jpg" />
```

#### You can combine values and child namespaces

```jsx
<Metadata
  og={{
    image: [
      'http://host.test/image1.jpg',
      { width: 300, height: 300 },
      'http://host.test/image2.jpg',
      'http://host.test/image3.jpg',
      { height: 1000 },
    ],
  }}
/>
// generates
<meta property="og:image" content="http://host.test/image1.jpg" />
<meta property="og:image:width" content="300" />
<meta property="og:image:height" content="300" />
<meta property="og:image" content="http://host.test/image2.jpg" />
<meta property="og:image" content="http://host.test/image3.jpg" />
<meta property="og:image:height" content="1000" />
```

#### If you define *any* `og` prop, we will copy any `title` and
`description` to an `og:title` and `og:description` tag.

```jsx
<Metadata title="My Website" og />
// generates
<meta name="title" content="My Website" />
<meta property="og:title" content="My Website" />
```

You can override this behavior by explicitly setting `og:title` to
`null`:

```jsx
<Metadata title="My Website" og={{ title: null }}/>
```

#### If you define *any* `og` prop, we will create an `og:type` set to
`website`:

```jsx
<Metadata og />
// generates
<meta property="og:type" content="website" />
```

You can override this behavior by explicitly setting `og:type`:

```jsx
<Metadata og={{ type: 'music:album' }}/>
```

#### If you define a `charSet` prop will we create the special `<meta
charSet>` tag:

```jsx
<Metadata charSet="utf-8" />
// generates
<meta charset="utf-8" />
```

#### If you define a `locale` prop we will inject the `locale` attribute
into the opening `<html>` tag:

```jsx
<Metadata locale="en-US" />
// generates
<html locale="en-US">
  <!-- ... -->
  <meta name="locale" content="en-US" />
```

---

This should allow the `<meta>` generation to be as flexible as possible,
allowing you to create twitter, facebook, and any other OpenGraph-like
properties in the future:

```jsx
<Metadata
  title="My Website"
  description="Lorem ipsum dolar sit amet..."
  charSet="utf-8"
  locale="en-US"
  og={{
    image: [
      'https://example.com/rock1.jpg',
      { width: 300, height: 300 },
      'https://example.com/rock2.jpg', 
      { width: 600, height: 600 },
    ],
  }}
  twitter={{
    card: 'summary',
    site: '@spoke',
    creator: '@cannikin',
  }}
  fb={{
    app_id: '1234567890',
  }}
/>
```

## Note for releasing

There’s a comment in the doc about “Prior to v7.0 Redwood provided a
`<MetaTags>` helper that has now been deprecated…” so if this goes in
earlier than 7 we’ll want to update that text.

---------

Co-authored-by: Daniel Choudhury <dannychoudhury@gmail.com>
Co-authored-by: Tobbe Lundberg <tobbe@tlundberg.com>
  • Loading branch information
3 people committed Dec 21, 2023
1 parent a0a80ff commit 10b005c
Show file tree
Hide file tree
Showing 50 changed files with 1,000 additions and 212 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'

const AboutPage = () => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'

type BlogPostPageProps = {
id: number
Expand All @@ -10,7 +10,7 @@ import BlogPostCell from 'src/components/BlogPostCell'
const BlogPostPage = ({ id }: BlogPostPageProps) => {
return (
<>
<MetaTags title={`Post ${id}`} description={`Description ${id}`} />
<Metadata title={`Post ${id}`} description={`Description ${id}`} og />

<BlogPostCell id={id} />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Label,
} from '@redwoodjs/forms'
import { Link, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'
import { useMutation } from '@redwoodjs/web'
import { toast, Toaster } from '@redwoodjs/web/toast'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'

const DoublePage = () => {
return (
<>
<MetaTags title="Double" description="Double page" />
<Metadata title="Double" description="Double page" og />

<h1 className="mb-1 mt-2 text-xl font-semibold">DoublePage</h1>
<p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'

import { Form, Label, TextField, Submit, FieldError } from '@redwoodjs/forms'
import { navigate, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'
import { toast, Toaster } from '@redwoodjs/web/toast'

import { useAuth } from 'src/auth'
Expand Down Expand Up @@ -39,7 +39,7 @@ const ForgotPasswordPage = () => {

return (
<>
<MetaTags title="Forgot Password" />
<Metadata title="Forgot Password" />

<main className="rw-main">
<Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'

import BlogPostsCell from 'src/components/BlogPostsCell'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
FieldError,
} from '@redwoodjs/forms'
import { Link, navigate, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'
import { toast, Toaster } from '@redwoodjs/web/toast'

import { useAuth } from 'src/auth'
Expand Down Expand Up @@ -46,7 +46,7 @@ const LoginPage = () => {

return (
<>
<MetaTags title="Login" />
<Metadata title="Login" />

<main className="rw-main">
<Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'

import { useAuth } from 'src/auth'

Expand All @@ -12,7 +12,7 @@ const ProfilePage = () => {

return (
<>
<MetaTags title="Profile" description="Profile page" />
<Metadata title="Profile" description="Profile page" og />

<h1 className="text-2xl">Profile</h1>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
FieldError,
} from '@redwoodjs/forms'
import { navigate, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'
import { toast, Toaster } from '@redwoodjs/web/toast'

import { useAuth } from 'src/auth'
Expand Down Expand Up @@ -59,7 +59,7 @@ const ResetPasswordPage = ({ resetToken }: { resetToken: string }) => {

return (
<>
<MetaTags title="Reset Password" />
<Metadata title="Reset Password" />

<main className="rw-main">
<Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Submit,
} from '@redwoodjs/forms'
import { Link, navigate, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
import { Metadata } from '@redwoodjs/web'
import { toast, Toaster } from '@redwoodjs/web/toast'

import { useAuth } from 'src/auth'
Expand Down Expand Up @@ -49,7 +49,7 @@ const SignupPage = () => {

return (
<>
<MetaTags title="Signup" />
<Metadata title="Signup" />

<main className="rw-main">
<Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
Expand Down
Loading

0 comments on commit 10b005c

Please sign in to comment.