diff --git a/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx b/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx index a09fda799fb9..6428b03989d7 100644 --- a/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx +++ b/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx @@ -1,5 +1,5 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const AboutPage = () => { return ( diff --git a/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx b/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx index bdc0a0cda146..415fbe886478 100644 --- a/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx +++ b/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx @@ -1,5 +1,5 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' type BlogPostPageProps = { id: number @@ -10,7 +10,7 @@ import BlogPostCell from 'src/components/BlogPostCell' const BlogPostPage = ({ id }: BlogPostPageProps) => { return ( <> - + diff --git a/__fixtures__/test-project/web/src/pages/ContactUsPage/ContactUsPage.tsx b/__fixtures__/test-project/web/src/pages/ContactUsPage/ContactUsPage.tsx index aab8a211e64e..529d72d8bfbc 100644 --- a/__fixtures__/test-project/web/src/pages/ContactUsPage/ContactUsPage.tsx +++ b/__fixtures__/test-project/web/src/pages/ContactUsPage/ContactUsPage.tsx @@ -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' diff --git a/__fixtures__/test-project/web/src/pages/DoublePage/DoublePage.tsx b/__fixtures__/test-project/web/src/pages/DoublePage/DoublePage.tsx index fc0366665a3a..fafa953fb3a4 100644 --- a/__fixtures__/test-project/web/src/pages/DoublePage/DoublePage.tsx +++ b/__fixtures__/test-project/web/src/pages/DoublePage/DoublePage.tsx @@ -1,9 +1,9 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const DoublePage = () => { return ( <> - +

DoublePage

diff --git a/__fixtures__/test-project/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx b/__fixtures__/test-project/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx index 8b383d5000ff..4d3f34fe28d3 100644 --- a/__fixtures__/test-project/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx +++ b/__fixtures__/test-project/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.tsx @@ -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' @@ -39,7 +39,7 @@ const ForgotPasswordPage = () => { return ( <> - +

diff --git a/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx b/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx index 4bf9f71d280b..290c7a31f29a 100644 --- a/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx +++ b/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx @@ -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' diff --git a/__fixtures__/test-project/web/src/pages/LoginPage/LoginPage.tsx b/__fixtures__/test-project/web/src/pages/LoginPage/LoginPage.tsx index d6562c8e50cf..a61d5ffaedee 100644 --- a/__fixtures__/test-project/web/src/pages/LoginPage/LoginPage.tsx +++ b/__fixtures__/test-project/web/src/pages/LoginPage/LoginPage.tsx @@ -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' @@ -46,7 +46,7 @@ const LoginPage = () => { return ( <> - +
diff --git a/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx b/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx index 39abea2fa43d..49911999021d 100644 --- a/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx +++ b/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx @@ -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' @@ -12,7 +12,7 @@ const ProfilePage = () => { return ( <> - +

Profile

diff --git a/__fixtures__/test-project/web/src/pages/ResetPasswordPage/ResetPasswordPage.tsx b/__fixtures__/test-project/web/src/pages/ResetPasswordPage/ResetPasswordPage.tsx index b5899cd3a218..191b39d43231 100644 --- a/__fixtures__/test-project/web/src/pages/ResetPasswordPage/ResetPasswordPage.tsx +++ b/__fixtures__/test-project/web/src/pages/ResetPasswordPage/ResetPasswordPage.tsx @@ -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' @@ -59,7 +59,7 @@ const ResetPasswordPage = ({ resetToken }: { resetToken: string }) => { return ( <> - +
diff --git a/__fixtures__/test-project/web/src/pages/SignupPage/SignupPage.tsx b/__fixtures__/test-project/web/src/pages/SignupPage/SignupPage.tsx index d1d0ac3a37f0..d92e41baeeb1 100644 --- a/__fixtures__/test-project/web/src/pages/SignupPage/SignupPage.tsx +++ b/__fixtures__/test-project/web/src/pages/SignupPage/SignupPage.tsx @@ -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' @@ -49,7 +49,7 @@ const SignupPage = () => { return ( <> - +
diff --git a/docs/docs/seo-head.md b/docs/docs/seo-head.md index 4b50350c6cdd..58baabebb5b3 100644 --- a/docs/docs/seo-head.md +++ b/docs/docs/seo-head.md @@ -2,37 +2,40 @@ description: Use meta tags to set page info for SEO --- -# SEO & Meta tags +# SEO & `` tags -## Add app title -You certainly want to change the title of your Redwood app. -You can start by adding or modify `title` inside `redwood.toml` +Search Engine Optimization is a dark art that some folks dedicate their entire lives to. We've add a couple of features to Redwood to make HTML-based SEO fairly simple. -```diff +## Adding a Title + +You certainly want to change the title of your Redwood app from the default of "Redwood App." You can start by adding or modifing `title` inside of `/redwood.toml` + +```diff title=redwood.toml [web] - title = "Redwood App" + title = "My Cool App" port = 8910 apiUrl = "/.redwood/functions" ``` + This title (the app title) is used by default for all your pages if you don't define another one. -It will also be use for the title template ! -### Title template +It will also be use for the title template. + +### Title Template + Now that you have the app title set, you probably want some consistence with the page title, that's what the title template is for. Add `titleTemplate` as a prop for `RedwoodProvider` to have a title template for every pages -In _web/src/App.\{tsx,js\}_ -```diff +```diff title=web/src/App.(tsx|jsx) - + /* ... */ ``` -You can write the format you like. +You can use whatever formatting you'd like in here. Some examples: -_Examples :_ ```jsx "%PageTitle | %AppTitle" => "Home Page | Redwood App" @@ -41,18 +44,13 @@ _Examples :_ "%PageTitle : %AppTitle" => "Home Page : Redwood App" ``` -So now in your page you only need to write the title of the page. +## Adding to Page `` -## Adding to page `` So you want to change the title of your page, or add elements to the `` of the page? We've got you! +Let's say you want to change the title of your About page, Redwood provides a built in `` component, which you can use like this: -Let's say you want to change the title of your About page, -Redwood provides a built in `` component, which you can use like this - - -In _AboutPage/AboutPage.\{tsx,js\}_ -```diff +```diff title=web/src/pages/AboutPage/AboutPage.(tsx|jsx) +import { Head } from '@redwoodjs/web' const AboutPage = () => { @@ -64,40 +62,50 @@ const AboutPage = () => { + ``` -You can include any valid `` tag in here that you like, but just to make things easier we also have a utility component [MetaTags](#setting-meta-tags-open-graph-directives). +You can include any valid `` tag in here that you like. However, Redwood also provides we also have a utility component [<Metadata>](#setting-meta-tags-and-opengraph-directives-with-metadata). + +:::caution `` Deprecation + +Prior to Redwood 7.0 this component was called `` and had several special hard-coded props like `ogContentUrl`, which didn't properly map to the OpenGraph spec. We'll still render `` for the foreseeable future, but it is deprecated and should migrate to `` if you have an existing app. + +::: + +### What About Nested Tags? -### What about nested tags? Redwood uses [react-helmet-async](https://github.com/staylor/react-helmet-async) underneath, which will use the tags furthest down your component tree. For example, if you set title in your Layout, and a title in your Page, it'll render the one in Page - this way you can override the tags you wish, while sharing the tags defined in Layout. +:::info Bots & `` Tags + +For these headers to appear to bots and scrapers e.g. for twitter to show your title, you have to make sure your page is prerendered. If your content is static you can use Redwood's built in [Prerender](prerender.md). For dynamic tags, check the [Dynamic head tags](#dynamic-tags) -> **Side note** -> for these headers to appear to bots and scrapers e.g. for twitter to show your title, you have to make sure your page is prerendered -> If your content is static you can use Redwood's built in [Prerender](prerender.md). For dynamic tags, check the [Dynamic head tags](#dynamic-tags) +::: -## Setting meta tags / open graph directives -Often we want to set more than just the title - most commonly to set "og" headers. Og standing for -[open graph](https://ogp.me/) of course. +## Setting `` Tags and OpenGraph Directives with `` -Redwood provides a convenience component `` to help you get all the relevant tags with one go (but you can totally choose to do them yourself) +Often we want to set more than just the title and description of the pageā€”most commonly [OpenGraph](https://ogp.me/) headers. + +Redwood provides a convenience component `` to help you create most of these `` tags for you swith a more concise syntax. But, you can also pass children and define any custom content that you want. + +Here's an example setting some common meta, including a page title, description, `og:image` and an `http-equiv`: -Here's an example setting some common headers, including how to set an `og:image` ```jsx import { MetaTags } from '@redwoodjs/web' const AboutPage = () => { return (
-

AboutPage

- + og={{ image: "https://example.com/images/og.png", url: "https://example.com/start" }} + robots="nofollow" + > + + + +

About Page

This is the about page!

) @@ -106,18 +114,213 @@ const AboutPage = () => { export default AboutPage ``` -This is great not just for link unfurling on say Facebook or Slack, but also for SEO. Take a look at the [source](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/MetaTags.tsx#L83) if you're curious what tags get set here. +This code would be transformed into this HTML and injected into the `` tag: + +```html +About page + + + + + + + + + +``` + +Setting an `og:image` is how sites like Facebook and Slack can show a preview of a URL when pasted into a post (also known an "unfurling"): + +![Typical URL unfurl](/img/facebook_unfurl.png) + +Sites like GitHub go a step farther than a generic image by actually creating an image for a repo on the fly, including details about the repo itself: + +![GitHub's og:image for the redwood repo](https://opengraph.githubassets.com/322ce8081bb85a86397a59494eab1c0fbe942b5104461f625e2c973c46ae4179/redwoodjs/redwood) + +If you want to write your own `` tags, skipping the interpolation that `` does for you, you can pass them as children to `` or just write them into the `` tag as normal. + +### `` Props + +For the most part `` creates simple `` tags based on the structure of the props you pass in. There are a couple of special behaviors described below. + +#### Plain Key/Value Props + +Any "plain" key/value prop will be turned into a `` tag with `name` and `content` attributes: + +```jsx + +// generates + +``` + +Child elements are just copied 1:1 to the resulting output: + +```jsx + + + +// generates + + +``` + +#### Passing Objects to Props + +Any props that contain an object will create a `` tag with `property` and `content` attributes, and the `property` being the names of the nested keys with a `:` between each: + +```jsx + +// generates + +``` + +This is most commonly used to create the "nested" structure that a spec like OpenGraph uses: + +```jsx + +// generates + +``` + +You can create multiple `` tags with the same name/property (allowed by the OpenGraph spec) by using an array: + +```jsx + +// generates + + +``` + +You can combine nested objects with strings to create any structure you like: + +```jsx + +// generates + + + + + + + +``` + +#### Special OpenGraph Helpers + +If you define _any_ `og` prop, we will copy any `title` and `description` to an `og:title` and `og:description`: + +```jsx + +// generates + + +``` + +You can override this behavior by explictly setting `og:title` or `og:description` to `null`: + +```jsx + +// generates + +``` + +Of course, if you don't want any auto-generated `og` tags, then don't include any `og` prop at all! + +In addition to `og:title` and `og:description`, if you define _any_ `og` prop we will generate an `og:type` set to `website`: + +```jsx + +// generates + +``` + +You can override the `og:type` by setting it directly: + +```jsx + +// generates + +``` + +#### Other Special Cases +If you define a `title` prop we will automatically prepend a `` tag to the output: + +```jsx +<Metadata title="My Website" /> +// generates +<title>My Website + +``` + +If you define a `charSet` prop we will create a `` tag with the `charset` attribute: + +```jsx + +// generates + +``` + +We simplifed some of the examples above by excluding the generated `` and `og:type` tags, so here's the real output if you included `title` and `og` props: + +```jsx +<Metadata title="My Website" og /> +// generates +<title>My Website + + + + +``` + +:::info Do I need to apply these same tags over and over in every page? + +Some `` tags, like `charset` or `locale` are probably applicable to the entire site, in which case it would be simpler to just include these once in your `index.html` instead of having to set them manually on each and every page/cell of your site. + +::: + +This should allow you to create a fairly full-featured set of `` tags with minimal special syntax! A typical `` invocation could look like: + +```jsx + +``` ## Dynamic tags -Great - so far we can see the changes, and bots will pick up our tags if we've prerendered the page, but what if I want to set the header based on the output of the Cell? -> **Prerendering cells**
-> As of v3.x, Redwood supports prerendering your [Cells](https://redwoodjs.com/docs/cells) with the data you were querying. For more information please refer [to this section](https://redwoodjs.com/docs/prerender#cell-prerendering). +Bots will pick up our tags if we've prerendered the page, but what if we want to set the `` based on the output of the Cell? +:::info Prerendering + +As of v3.x, Redwood supports prerendering your [Cells](https://redwoodjs.com/docs/cells) with the data you were querying. For more information please refer [to this section](https://redwoodjs.com/docs/prerender#cell-prerendering). + +::: + +Let's say in our `PostCell`, we want to set the title to match the `Post`. -Let's say in our PostCell, we want to set the title to match the Post. ```jsx +import { Metadata } from '@redwoodjs/web' + import Post from 'src/components/Post/Post' export const QUERY = gql` @@ -139,7 +342,7 @@ export const Empty = /* ... */ export const Success = ({ post }) => { return ( <> - { ) } ``` -Once the success component renders, it'll update your page's title and set the relevant meta tags for you! + +Once the `Success` component renders, it will update your page's `` and set the relevant `<meta>` tags for you! diff --git a/docs/static/img/facebook_unfurl.png b/docs/static/img/facebook_unfurl.png new file mode 100644 index 000000000000..780eca0b0697 Binary files /dev/null and b/docs/static/img/facebook_unfurl.png differ diff --git a/packages/cli/src/commands/generate/__tests__/helpers.test.js b/packages/cli/src/commands/generate/__tests__/helpers.test.js index 35bf51fb02cd..0e2b7179cd7a 100644 --- a/packages/cli/src/commands/generate/__tests__/helpers.test.js +++ b/packages/cli/src/commands/generate/__tests__/helpers.test.js @@ -9,12 +9,12 @@ import * as helpers from '../helpers' import * as page from '../page/page' const PAGE_TEMPLATE_OUTPUT = `import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const FooBarPage = () => { return ( <> - <MetaTags title="FooBar" description="FooBar page" /> + <Metadata title="FooBar" description="FooBar page" /> <h1>FooBarPage</h1> <p> diff --git a/packages/cli/src/commands/generate/dbAuth/__tests__/__snapshots__/dbAuth.test.js.snap b/packages/cli/src/commands/generate/dbAuth/__tests__/__snapshots__/dbAuth.test.js.snap index 2007b4e010c9..2229d11ab60c 100644 --- a/packages/cli/src/commands/generate/dbAuth/__tests__/__snapshots__/dbAuth.test.js.snap +++ b/packages/cli/src/commands/generate/dbAuth/__tests__/__snapshots__/dbAuth.test.js.snap @@ -13,7 +13,7 @@ exports[`dbAuth handler produces the correct files with custom password set via 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' @@ -48,7 +48,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Secret" /> + <Metadata title="Forgot Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -115,7 +115,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' @@ -151,7 +151,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -251,7 +251,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' @@ -302,7 +302,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Secret" /> + <Metadata title="Reset Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -376,7 +376,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' @@ -414,7 +414,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -501,7 +501,7 @@ exports[`dbAuth handler produces the correct files with custom password set via 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' @@ -536,7 +536,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Secret" /> + <Metadata title="Forgot Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -603,7 +603,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' @@ -639,7 +639,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -739,7 +739,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' @@ -790,7 +790,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Secret" /> + <Metadata title="Reset Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -864,7 +864,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' @@ -902,7 +902,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -989,7 +989,7 @@ exports[`dbAuth handler produces the correct files with custom username and pass 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' @@ -1024,7 +1024,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Secret" /> + <Metadata title="Forgot Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1091,7 +1091,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' @@ -1127,7 +1127,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1227,7 +1227,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' @@ -1278,7 +1278,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Secret" /> + <Metadata title="Reset Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1352,7 +1352,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' @@ -1390,7 +1390,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1477,7 +1477,7 @@ exports[`dbAuth handler produces the correct files with custom username and pass 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' @@ -1512,7 +1512,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Secret" /> + <Metadata title="Forgot Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1579,7 +1579,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' @@ -1615,7 +1615,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1715,7 +1715,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' @@ -1766,7 +1766,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Secret" /> + <Metadata title="Reset Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1840,7 +1840,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' @@ -1878,7 +1878,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -1965,7 +1965,7 @@ exports[`dbAuth handler produces the correct files with custom username and pass 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' @@ -2000,7 +2000,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Secret" /> + <Metadata title="Forgot Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -2067,7 +2067,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' @@ -2299,7 +2299,7 @@ const LoginPage = ({ type }) => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -2335,7 +2335,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' @@ -2386,7 +2386,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Secret" /> + <Metadata title="Reset Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -2460,7 +2460,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' @@ -2498,7 +2498,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -2585,7 +2585,7 @@ exports[`dbAuth handler produces the correct files with custom username and pass 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' @@ -2620,7 +2620,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Secret" /> + <Metadata title="Forgot Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -2687,7 +2687,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' @@ -2723,7 +2723,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -2823,7 +2823,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' @@ -2874,7 +2874,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Secret" /> + <Metadata title="Reset Secret" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -2948,7 +2948,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' @@ -2986,7 +2986,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3073,7 +3073,7 @@ exports[`dbAuth handler produces the correct files with custom username set via 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' @@ -3110,7 +3110,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Password" /> + <Metadata title="Forgot Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3179,7 +3179,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' @@ -3215,7 +3215,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3315,7 +3315,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' @@ -3366,7 +3366,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Password" /> + <Metadata title="Reset Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3442,7 +3442,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' @@ -3480,7 +3480,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3567,7 +3567,7 @@ exports[`dbAuth handler produces the correct files with custom username set via 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' @@ -3604,7 +3604,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Password" /> + <Metadata title="Forgot Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3673,7 +3673,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' @@ -3709,7 +3709,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3809,7 +3809,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' @@ -3860,7 +3860,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Password" /> + <Metadata title="Reset Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -3936,7 +3936,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' @@ -3974,7 +3974,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -4061,7 +4061,7 @@ exports[`dbAuth handler produces the correct files with default labels 1`] = ` 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' @@ -4098,7 +4098,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Password" /> + <Metadata title="Forgot Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -4167,7 +4167,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' @@ -4203,7 +4203,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -4303,7 +4303,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' @@ -4354,7 +4354,7 @@ const ResetPasswordPage = ({ resetToken }) => { return ( <> - <MetaTags title="Reset Password" /> + <Metadata title="Reset Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> @@ -4430,7 +4430,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' @@ -4468,7 +4468,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/cli/src/commands/generate/dbAuth/templates/forgotPassword.tsx.template b/packages/cli/src/commands/generate/dbAuth/templates/forgotPassword.tsx.template index cf72f6efcb1e..06c34f0eaf64 100644 --- a/packages/cli/src/commands/generate/dbAuth/templates/forgotPassword.tsx.template +++ b/packages/cli/src/commands/generate/dbAuth/templates/forgotPassword.tsx.template @@ -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' @@ -39,7 +39,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot ${passwordTitleCase}" /> + <Metadata title="Forgot ${passwordTitleCase}" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/cli/src/commands/generate/dbAuth/templates/login.tsx.template b/packages/cli/src/commands/generate/dbAuth/templates/login.tsx.template index b09688c201e5..88fac90acf42 100644 --- a/packages/cli/src/commands/generate/dbAuth/templates/login.tsx.template +++ b/packages/cli/src/commands/generate/dbAuth/templates/login.tsx.template @@ -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' @@ -43,7 +43,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/cli/src/commands/generate/dbAuth/templates/login.webAuthn.tsx.template b/packages/cli/src/commands/generate/dbAuth/templates/login.webAuthn.tsx.template index eadd3d4222c9..bf25bef4cb64 100644 --- a/packages/cli/src/commands/generate/dbAuth/templates/login.webAuthn.tsx.template +++ b/packages/cli/src/commands/generate/dbAuth/templates/login.webAuthn.tsx.template @@ -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' @@ -241,7 +241,7 @@ const LoginPage = ({ type }) => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/cli/src/commands/generate/dbAuth/templates/resetPassword.tsx.template b/packages/cli/src/commands/generate/dbAuth/templates/resetPassword.tsx.template index 232eaf8de0f2..bd84a9783d6b 100644 --- a/packages/cli/src/commands/generate/dbAuth/templates/resetPassword.tsx.template +++ b/packages/cli/src/commands/generate/dbAuth/templates/resetPassword.tsx.template @@ -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' @@ -59,7 +59,7 @@ const ResetPasswordPage = ({ resetToken }: { resetToken: string }) => { return ( <> - <MetaTags title="Reset ${passwordTitleCase}" /> + <Metadata title="Reset ${passwordTitleCase}" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/cli/src/commands/generate/dbAuth/templates/signup.tsx.template b/packages/cli/src/commands/generate/dbAuth/templates/signup.tsx.template index e84e61ce0012..e3e1196f9b34 100644 --- a/packages/cli/src/commands/generate/dbAuth/templates/signup.tsx.template +++ b/packages/cli/src/commands/generate/dbAuth/templates/signup.tsx.template @@ -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' @@ -45,7 +45,7 @@ const SignupPage = () => { return ( <> - <MetaTags title="Signup" /> + <Metadata title="Signup" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap b/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap index 9d727c16f3ea..7557712c9a30 100644 --- a/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap +++ b/packages/cli/src/commands/generate/page/__tests__/__snapshots__/page.test.js.snap @@ -2,12 +2,12 @@ exports[`Plural word files creates a page component with a plural word for name 1`] = ` "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const CatsPage = () => { return ( <> - <MetaTags title="Cats" description="Cats page" /> + <Metadata title="Cats" description="Cats page" /> <h1>CatsPage</h1> <p> @@ -27,12 +27,12 @@ export default CatsPage exports[`Single world files creates a page component 1`] = ` "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const HomePage = () => { return ( <> - <MetaTags title="Home" description="Home page" /> + <Metadata title="Home" description="Home page" /> <h1>HomePage</h1> <p> @@ -83,7 +83,7 @@ describe('HomePage', () => { exports[`TS Files TS Params 1`] = ` "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' type TsParamFilesPageProps = { id: string @@ -92,7 +92,7 @@ type TsParamFilesPageProps = { const TsParamFilesPage = ({ id }: TsParamFilesPageProps) => { return ( <> - <MetaTags title="TsParamFiles" description="TsParamFiles page" /> + <Metadata title="TsParamFiles" description="TsParamFiles page" /> <h1>TsParamFilesPage</h1> <p> @@ -114,7 +114,7 @@ export default TsParamFilesPage exports[`TS Files TS Params with type 1`] = ` "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' type TsParamTypeFilesPageProps = { id: number @@ -123,7 +123,7 @@ type TsParamTypeFilesPageProps = { const TsParamTypeFilesPage = ({ id }: TsParamTypeFilesPageProps) => { return ( <> - <MetaTags title="TsParamTypeFiles" description="TsParamTypeFiles page" /> + <Metadata title="TsParamTypeFiles" description="TsParamTypeFiles page" /> <h1>TsParamTypeFilesPage</h1> <p> @@ -151,12 +151,12 @@ export default TsParamTypeFilesPage exports[`TS Files generates typescript pages 1`] = ` "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const TsFilesPage = () => { return ( <> - <MetaTags title="TsFiles" description="TsFiles page" /> + <Metadata title="TsFiles" description="TsFiles page" /> <h1>TsFilesPage</h1> <p> @@ -249,12 +249,12 @@ describe('HomePage', () => { exports[`handler file generation 3`] = ` { "fileContent": "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const HomePage = () => { return ( <> - <MetaTags title="Home" description="Home page" /> + <Metadata title="Home" description="Home page" /> <h1>HomePage</h1> <p> @@ -333,12 +333,12 @@ describe('PostPage', () => { exports[`handler file generation with route params 3`] = ` { "fileContent": "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const PostPage = ({ id }) => { return ( <> - <MetaTags title="Post" description="Post page" /> + <Metadata title="Post" description="Post page" /> <h1>PostPage</h1> <p> @@ -380,12 +380,12 @@ export default Routes", exports[`multiWorldFiles creates a page component 1`] = ` "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const ContactUsPage = () => { return ( <> - <MetaTags title="ContactUs" description="ContactUs page" /> + <Metadata title="ContactUs" description="ContactUs page" /> <h1>ContactUsPage</h1> <p> @@ -436,12 +436,12 @@ describe('ContactUsPage', () => { exports[`paramFiles creates a page component with params 1`] = ` "import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const PostPage = ({ id }) => { return ( <> - <MetaTags title="Post" description="Post page" /> + <Metadata title="Post" description="Post page" /> <h1>PostPage</h1> <p> diff --git a/packages/cli/src/commands/generate/page/page.js b/packages/cli/src/commands/generate/page/page.js index f18fdbcae397..b20425d7e5b6 100644 --- a/packages/cli/src/commands/generate/page/page.js +++ b/packages/cli/src/commands/generate/page/page.js @@ -264,8 +264,8 @@ export const handler = async ({ task: (ctx, task) => { task.title = `One more thing...\n\n` + - ` ${c.warning('Page created! A note about <MetaTags>:')}\n\n` + - ` At the top of your newly created page is a <MetaTags> component,\n` + + ` ${c.warning('Page created! A note about <Metadata>:')}\n\n` + + ` At the top of your newly created page is a <Metadata> component,\n` + ` which contains the title and description for your page, essential\n` + ` to good SEO. Check out this page for best practices: \n\n` + ` https://developers.google.com/search/docs/advanced/appearance/good-titles-snippets\n` diff --git a/packages/cli/src/commands/generate/page/templates/page.tsx.template b/packages/cli/src/commands/generate/page/templates/page.tsx.template index fc16315a0a96..046843373649 100644 --- a/packages/cli/src/commands/generate/page/templates/page.tsx.template +++ b/packages/cli/src/commands/generate/page/templates/page.tsx.template @@ -1,5 +1,5 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' <% if (paramName !== '') { %> type ${pascalName}PageProps = { ${paramName}: ${paramType} @@ -8,7 +8,7 @@ type ${pascalName}PageProps = { const ${pascalName}Page = (<%- paramName === '' ? '' : `${propParam}: ${pascalName}PageProps` %>) => { return ( <> - <MetaTags title="${pascalName}" description="${pascalName} page" /> + <Metadata title="${pascalName}" description="${pascalName} page" /> <h1>${pascalName}Page</h1> <p> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js index 2e0a2475db25..118a3c98c9c8 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react' import { navigate, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import { toast, Toaster } from '@redwoodjs/web/toast' import { FieldError, Form, Label, Submit, TextField } from '@redwoodjs/forms' @@ -38,7 +38,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Password" /> + <Metadata title="Forgot Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js index d63d1b4eebaf..1feebfb2a282 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js @@ -1,4 +1,4 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import Hero from 'src/components/Hero/Hero' import { useTranslation, Trans } from 'react-i18next' import Highlight from 'react-highlight' @@ -8,7 +8,7 @@ const HomePage = () => { return ( <> - <MetaTags + <Metadata title="RedwoodJS: The Full-stack JS Framework" description="Grow from side project to startup with RedwoodJS. Combines React, GraphQL and Prisma for a full-stack app framework." /> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx index 928905af16f6..85b781ddf817 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx @@ -1,11 +1,11 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobProfilesCell from 'src/components/Jobs/JobProfilesCell' const AllJobProfilesPage = () => { return ( <> - <MetaTags title="AllJobProfiles" description="AllJobProfiles page" /> + <Metadata title="AllJobProfiles" description="AllJobProfiles page" /> <section className="max-w-screen-lg mx-auto mt-36 mb-24"> <header className="text-center"> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx index a584b40d7e6e..3ffce2b712ce 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx @@ -1,11 +1,11 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobsCell from 'src/components/Jobs/JobsCell' const AllJobsPage = () => { return ( <> - <MetaTags title="AllJobs" description="AllJobs page" /> + <Metadata title="AllJobs" description="AllJobs page" /> <section className="max-w-screen-lg mx-auto mt-36 mb-24"> <header className="text-center"> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx index 874baaabac7f..8c9ae6fe18bc 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx @@ -1,10 +1,10 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobCell from 'src/components/Jobs/JobCell' const JobPage = ({ id }) => { return ( <> - <MetaTags + <Metadata title="Job" description="Job opening for RedwoodJS developers" /> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx index 0594cde40f5c..10986e177626 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx @@ -1,10 +1,10 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobProfileCell from 'src/components/Jobs/JobProfileCell' const JobProfilePage = ({ id }) => { return ( <> - <MetaTags + <Metadata title="Job Profile" description="RedwoodJS devs available for work" /> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx index 3ddde01ef36d..25574d6521e5 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx @@ -1,12 +1,12 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobsCell from 'src/components/Jobs/JobsCell' import JobProfilesCell from 'src/components/Jobs/JobProfilesCell' const JobsPage = () => { return ( <> - <MetaTags + <Metadata title="Jobs" description="Want to get paid to write RedwoodJS?" /> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx index c55f55c520cc..a6d26888f38d 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx @@ -1,4 +1,4 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const CREATE_JOB = gql` @@ -12,7 +12,7 @@ const CREATE_JOB = gql` const NewJobPage = ({ token }) => { return ( <> - <MetaTags + <Metadata title="Post a Job" description="Looking to hire RedwoodJS developers? Post on the Redwood job board!" /> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx index 4d9b60e36482..b0add733e726 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { navigate, routes } from '@redwoodjs/router' -import { MetaTags, useMutation } from '@redwoodjs/web' +import { Metadata, useMutation } from '@redwoodjs/web' import { toast } from '@redwoodjs/web/toast' import JobProfileForm from 'src/components/Jobs/JobProfileForm' @@ -38,7 +38,7 @@ const NewJobProfilePage = () => { return ( <> - <MetaTags + <Metadata title="Post a Job" description="Looking to hire RedwoodJS developers? Post on the Redwood job board!" /> diff --git a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js index 80202a02cb7f..e729b4e00fab 100644 --- a/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js +++ b/packages/internal/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js @@ -9,7 +9,7 @@ import { TextField, } from '@redwoodjs/forms' import { useAuth } from '@redwoodjs/auth' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import { toast, Toaster } from '@redwoodjs/web/toast' const LoginPage = () => { @@ -40,7 +40,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js index 2e0a2475db25..118a3c98c9c8 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/ForgotPasswordPage/ForgotPasswordPage.js @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react' import { navigate, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import { toast, Toaster } from '@redwoodjs/web/toast' import { FieldError, Form, Label, Submit, TextField } from '@redwoodjs/forms' @@ -38,7 +38,7 @@ const ForgotPasswordPage = () => { return ( <> - <MetaTags title="Forgot Password" /> + <Metadata title="Forgot Password" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js index d63d1b4eebaf..1feebfb2a282 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/HomePage/HomePage.js @@ -1,4 +1,4 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import Hero from 'src/components/Hero/Hero' import { useTranslation, Trans } from 'react-i18next' import Highlight from 'react-highlight' @@ -8,7 +8,7 @@ const HomePage = () => { return ( <> - <MetaTags + <Metadata title="RedwoodJS: The Full-stack JS Framework" description="Grow from side project to startup with RedwoodJS. Combines React, GraphQL and Prisma for a full-stack app framework." /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx index 928905af16f6..85b781ddf817 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobProfilesPage/AllJobProfilesPage.tsx @@ -1,11 +1,11 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobProfilesCell from 'src/components/Jobs/JobProfilesCell' const AllJobProfilesPage = () => { return ( <> - <MetaTags title="AllJobProfiles" description="AllJobProfiles page" /> + <Metadata title="AllJobProfiles" description="AllJobProfiles page" /> <section className="max-w-screen-lg mx-auto mt-36 mb-24"> <header className="text-center"> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx index a584b40d7e6e..3ffce2b712ce 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/AllJobsPage/AllJobsPage.tsx @@ -1,11 +1,11 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobsCell from 'src/components/Jobs/JobsCell' const AllJobsPage = () => { return ( <> - <MetaTags title="AllJobs" description="AllJobs page" /> + <Metadata title="AllJobs" description="AllJobs page" /> <section className="max-w-screen-lg mx-auto mt-36 mb-24"> <header className="text-center"> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx index 874baaabac7f..8c9ae6fe18bc 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobPage/JobPage.tsx @@ -1,10 +1,10 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobCell from 'src/components/Jobs/JobCell' const JobPage = ({ id }) => { return ( <> - <MetaTags + <Metadata title="Job" description="Job opening for RedwoodJS developers" /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx index 0594cde40f5c..10986e177626 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobProfilePage/JobProfilePage.tsx @@ -1,10 +1,10 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobProfileCell from 'src/components/Jobs/JobProfileCell' const JobProfilePage = ({ id }) => { return ( <> - <MetaTags + <Metadata title="Job Profile" description="RedwoodJS devs available for work" /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx index 3ddde01ef36d..25574d6521e5 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/JobsPage/JobsPage.tsx @@ -1,12 +1,12 @@ import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import JobsCell from 'src/components/Jobs/JobsCell' import JobProfilesCell from 'src/components/Jobs/JobProfilesCell' const JobsPage = () => { return ( <> - <MetaTags + <Metadata title="Jobs" description="Want to get paid to write RedwoodJS?" /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx index c55f55c520cc..a6d26888f38d 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobPage/NewJobPage.tsx @@ -1,4 +1,4 @@ -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const CREATE_JOB = gql` @@ -12,7 +12,7 @@ const CREATE_JOB = gql` const NewJobPage = ({ token }) => { return ( <> - <MetaTags + <Metadata title="Post a Job" description="Looking to hire RedwoodJS developers? Post on the Redwood job board!" /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx index 4d9b60e36482..b0add733e726 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/Jobs/NewJobProfilePage/NewJobProfilePage.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { navigate, routes } from '@redwoodjs/router' -import { MetaTags, useMutation } from '@redwoodjs/web' +import { Metadata, useMutation } from '@redwoodjs/web' import { toast } from '@redwoodjs/web/toast' import JobProfileForm from 'src/components/Jobs/JobProfileForm' @@ -38,7 +38,7 @@ const NewJobProfilePage = () => { return ( <> - <MetaTags + <Metadata title="Post a Job" description="Looking to hire RedwoodJS developers? Post on the Redwood job board!" /> diff --git a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js index 80202a02cb7f..e729b4e00fab 100644 --- a/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js +++ b/packages/vite/src/__tests__/fixtures/nestedPages/web/src/pages/LoginPage/LoginPage.js @@ -9,7 +9,7 @@ import { TextField, } from '@redwoodjs/forms' import { useAuth } from '@redwoodjs/auth' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' import { toast, Toaster } from '@redwoodjs/web/toast' const LoginPage = () => { @@ -40,7 +40,7 @@ const LoginPage = () => { return ( <> - <MetaTags title="Login" /> + <Metadata title="Login" /> <main className="rw-main"> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> diff --git a/packages/web/src/components/MetaTags.tsx b/packages/web/src/components/MetaTags.tsx index 1b1c5ebd492f..c73849dca9b5 100644 --- a/packages/web/src/components/MetaTags.tsx +++ b/packages/web/src/components/MetaTags.tsx @@ -65,6 +65,7 @@ interface MetaTagsProps { * using the open graph protocol https://ogp.me/ * @example * <MetaTags title="About Page" ogContentUrl="/static/about-og.png"/> + * @deprecated Please use <Metadata> instead */ export const MetaTags = (props: MetaTagsProps) => { const { diff --git a/packages/web/src/components/Metadata.test.tsx b/packages/web/src/components/Metadata.test.tsx new file mode 100644 index 000000000000..9e3174fa8da8 --- /dev/null +++ b/packages/web/src/components/Metadata.test.tsx @@ -0,0 +1,471 @@ +import { render } from '@testing-library/react' + +import '@testing-library/jest-dom' +import { Metadata } from './Metadata' + +// DOCS: can return a structured object from the database and just give it to `og` and it works + +describe('Metadata', () => { + beforeAll(() => { + // TODO: remove this once SSR is released + // this is just a workaround so we force the usage of PortalHead instead of Helmet for testing + globalThis.RWJS_ENV = { + RWJS_EXP_STREAMING_SSR: true, + } + }) + + it('renders nothing if no props or children', () => { + const input = <Metadata /> + const output = <></> + + expect( + render(input, { container: document.head }).container.innerHTML + ).toEqual(render(output, { container: document.head }).container.innerHTML) + }) + + it('renders non-namespaced props', () => { + const input = <Metadata title="My Title" /> + const output = ( + <> + <meta name="title" content="My Title" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders children', () => { + const input = ( + <Metadata> + <meta name="foo" content="bar" /> + </Metadata> + ) + const output = ( + <> + <meta name="foo" content="bar" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders props and children', () => { + const input = ( + <Metadata title="My Title"> + <meta httpEquiv="refresh" content="30" /> + </Metadata> + ) + const output = ( + <> + <meta name="title" content="My Title" /> + <meta httpEquiv="refresh" content="30" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders first-level namespaced props', () => { + const input = <Metadata og={{ image: 'http://host.test/image.jpg' }} /> + const output = ( + <> + <meta property="og:image" content="http://host.test/image.jpg" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders multiple first-level namespaced props', () => { + const input = ( + <Metadata + og={{ image: 'http://host.test/image.jpg' }} + twitter={{ card: 'summary' }} + /> + ) + const output = ( + <> + <meta property="og:image" content="http://host.test/image.jpg" /> + <meta property="twitter:card" content="summary" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders second-level namespaced props', () => { + const input = <Metadata og={{ image: { width: 100 } }} /> + const output = ( + <> + <meta property="og:image:width" content="100" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders combined first-level and second-level namespaced props', () => { + const input = ( + <Metadata + og={{ + image: 'http://host.test/image.jpg', + display: { type: 'screen' }, + }} + /> + ) + const output = ( + <> + <meta property="og:image" content="http://host.test/image.jpg" /> + <meta property="og:display:type" content="screen" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders an array of non-namespaced props', () => { + const input = <Metadata title={['Title 1', 'Title 2']} /> + const output = ( + <> + <meta name="title" content="Title 1" /> + <meta name="title" content="Title 2" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders an array of namespaced props', () => { + const input = ( + <Metadata + og={{ + image: ['http://host.test/image1.jpg', 'http://host.test/image2.jpg'], + }} + /> + ) + const output = ( + <> + <meta property="og:image" content="http://host.test/image1.jpg" /> + <meta property="og:image" content="http://host.test/image2.jpg" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders a mixture of namespaced array strings and objects', () => { + const input = ( + <Metadata + og={{ + image: [ + 'http://host.test/image1.jpg', + { width: 1024, height: 768 }, + 'http://host.test/image2.jpg', + 'http://host.test/image3.jpg', + { width: 640 }, + { height: 480 }, + ], + }} + /> + ) + const output = ( + <> + <meta property="og:image" content="http://host.test/image1.jpg" /> + <meta property="og:image:width" content="1024" /> + <meta property="og:image:height" content="768" /> + <meta property="og:image" content="http://host.test/image2.jpg" /> + <meta property="og:image" content="http://host.test/image3.jpg" /> + <meta property="og:image:width" content="640" /> + <meta property="og:image:height" content="480" /> + </> + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('adds a <title> tag if `title` attribute present', () => { + const input = <Metadata title="My Title" /> + const output = ( + <> + <title>My Title + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('adds multiple tags in proper order if `title` attribute is an array', () => { + const input = <Metadata title={['Title 1', 'Title 2']} /> + const output = ( + <> + <title>Title 1 + Title 2 + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('adds an `og:title` tag if any `og` key present', () => { + const input = + const output = ( + <> + + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('does not automatically add `og:title` if already present', () => { + const input = + const output = ( + <> + + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('does not automatically add `og:title` if set to null', () => { + const input = + const output = ( + <> + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).not.toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('adds an `og:description` tag if any `og` key present', () => { + const input = + const output = ( + <> + + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('does not automatically add `og:description` if already present', () => { + const input = ( + + ) + const output = ( + <> + + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('does not automatically add `og:description` if set to null', () => { + const input = ( + + ) + const output = ( + <> + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).not.toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('adds an `og:type` tag if any `og` key present', () => { + const input = + const output = ( + <> + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('does not automatically add `og:type` if already present', () => { + const input = + const output = ( + <> + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('does not automatically add `og:type` if set to null', () => { + const input = + const output = ( + <> + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).not.toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('does not create a standard name/content tag for the `charSet` prop', () => { + const input = + const output = ( + <> + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).not.toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('adds a special `charSet` meta tag if `charSet` prop present', () => { + const input = + const output = ( + <> + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toContain( + render(output, { container: document.head }).container.innerHTML + ) + }) + + it('renders a typical collection of tags', () => { + const input = ( + + ) + const output = ( + <> + My Title + + + + + + + + + + + + + + ) + + expect( + render(input, { container: document.head }).container.innerHTML + ).toEqual(render(output, { container: document.head }).container.innerHTML) + }) +}) diff --git a/packages/web/src/components/Metadata.tsx b/packages/web/src/components/Metadata.tsx new file mode 100644 index 000000000000..7b555df7d58d --- /dev/null +++ b/packages/web/src/components/Metadata.tsx @@ -0,0 +1,111 @@ +import React from 'react' + +import { Head as HelmetHead } from '../index' + +// Ideally we wouldn't include this for non experiment builds +// But.... not worth the effort to remove it from bundle atm +import PortalHead from './PortalHead' + +type ValueOrCollection = T | ValueOrCollection[] | Record +type ParentValue = ValueOrCollection + +const EXCLUDE_PROPS = ['charSet'] + +const propToMetaTag = ( + parentKey: string, + parentValue: ParentValue, + options: { attr: 'name' | 'property' } +): JSX.Element | JSX.Element[] => { + if (Array.isArray(parentValue)) { + // array of attributes + return parentValue.flatMap((value) => { + return propToMetaTag(parentKey, value, options) + }) + } else if (typeof parentValue === 'object') { + // namespaced attributes, name attribute changes to 'property' + return Object.entries(parentValue) + .filter(([_, v]) => v !== null) + .flatMap(([key, value]) => { + return propToMetaTag(`${parentKey}:${key}`, value, { attr: 'property' }) + }) + } else { + // plain text + const attributes = { + [options['attr']]: parentKey, + content: parentValue as string, + } + return + } +} + +/** + * Add commonly used tags for unfurling/seo purposes + * using the open graph protocol https://ogp.me/ + * @example + * + */ +export const Metadata = (props: Record) => { + const { children, ...metaProps } = props + + let Head: typeof HelmetHead | typeof PortalHead = HelmetHead + + if (RWJS_ENV.RWJS_EXP_STREAMING_SSR) { + Head = PortalHead + } + + const tags: JSX.Element[] = Object.entries(metaProps) + .filter( + ([key, value]) => + !EXCLUDE_PROPS.includes(key) && + value !== null && + (key !== 'og' || value !== true) + ) + .flatMap(([key, value]) => { + return propToMetaTag(key, value, { attr: 'name' }) + }) + .filter((tag) => !!tag) + + // custom overrides + if (metaProps.title) { + ;[metaProps.title] + .flat() + .reverse() + .map((title) => { + tags.unshift({title}) + }) + } + + if (metaProps.charSet) { + tags.push() + } + + if (metaProps.og) { + // add title and og:title + if (metaProps.title && !metaProps.og.title && metaProps.og.title !== null) { + tags.push() + } + + // add og:description + if ( + metaProps.description && + !metaProps.og.description && + metaProps.og.description !== null + ) { + tags.push( + + ) + } + + // add og:type + if (!metaProps.og.type && metaProps.og.type !== null) { + tags.push() + } + } + + return ( + + {tags.map((tag, i) => React.cloneElement(tag, { key: i }))} + {children} + + ) +} diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index 30aeafefb305..908c04485573 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -31,6 +31,7 @@ export * from './graphql' export * from './components/RedwoodProvider' export * from './components/MetaTags' +export * from './components/Metadata' export { Helmet as Head, Helmet } from 'react-helmet-async' export * from './components/htmlTags' diff --git a/tasks/test-project/codemods/blogPostPage.js b/tasks/test-project/codemods/blogPostPage.js index 80bb9cb5ba45..6fd337b6ad71 100644 --- a/tasks/test-project/codemods/blogPostPage.js +++ b/tasks/test-project/codemods/blogPostPage.js @@ -2,7 +2,7 @@ const body = ` { return ( <> - + diff --git a/tasks/test-project/codemods/profilePage.js b/tasks/test-project/codemods/profilePage.js index 3da4567fa0ca..21e3b12977ca 100644 --- a/tasks/test-project/codemods/profilePage.js +++ b/tasks/test-project/codemods/profilePage.js @@ -6,7 +6,7 @@ if (loading) { return ( <> - +

Profile

diff --git a/tasks/test-project/tasks.js b/tasks/test-project/tasks.js index 4457cdaccf56..a5a9676f6136 100644 --- a/tasks/test-project/tasks.js +++ b/tasks/test-project/tasks.js @@ -489,12 +489,12 @@ async function apiTasks(outputPath, { verbose, linkWithLatestFwBuild }) { const createPage = createBuilder('yarn redwood g page') await createPage('double') - const doublePageContent = `import { MetaTags } from '@redwoodjs/web' + const doublePageContent = `import { Metadata } from '@redwoodjs/web' const DoublePage = () => { return ( <> - +

DoublePage

diff --git a/tasks/test-project/tui-tasks.js b/tasks/test-project/tui-tasks.js index 8c4fedb64656..2001faf64383 100644 --- a/tasks/test-project/tui-tasks.js +++ b/tasks/test-project/tui-tasks.js @@ -547,12 +547,12 @@ async function apiTasks(outputPath, { linkWithLatestFwBuild }) { const createPage = createBuilder('yarn redwood g page') await createPage('double') - const doublePageContent = `import { MetaTags } from '@redwoodjs/web' + const doublePageContent = `import { Metadata } from '@redwoodjs/web' const DoublePage = () => { return ( <> - +

DoublePage