Layouts

One way to solve the <header> dilemma would be to create a <Header> component and include it in both HomePage and AboutPage. That works, but is there a better solution? Ideally there should only be one reference to the <header> anywhere in our code.

When you look at these two pages what do they really care about? They have some content they want to display. They really shouldn't have to care what comes before (like a <header>) or after (like a <footer>). That's exactly what layouts do: they wrap your pages in a component that then renders the page as its child:

Layouts structure diagram

Let's create a layout to hold that <header>:

yarn redwood g layout blog

From now on we'll use the shorter g alias instead of generate

That created web/src/layouts/BlogLayout/BlogLayout.js and an associated test file. We're calling this the "blog" layout because we may have other layouts at some point in the future (an "admin" layout, perhaps?).

Cut the <header> from both HomePage and AboutPage and paste it in the layout instead. Let's take out the duplicated <main> tag as well:

// web/src/layouts/BlogLayout/BlogLayout.js

import { Link, routes } from '@redwoodjs/router'

const BlogLayout = ({ children }) => {
  return (
    <>
      <header>
        <h1>Redwood Blog</h1>
        <nav>
          <ul>
            <li>
              <Link to={routes.about()}>About</Link>
            </li>
          </ul>
        </nav>
      </header>
      <main>{children}</main>
    </>
  )
}

export default BlogLayout

children is where the magic will happen. Any page content given to the layout will be rendered here. Back to HomePage and AboutPage, we add a <BlogLayout> wrapper and now they're back to focusing on the content they care about (we can remove the import for Link and routes from HomePage since those are in the Layout instead):

// web/src/pages/HomePage/HomePage.js

import BlogLayout from 'src/layouts/BlogLayout'

const HomePage = () => {
  return <BlogLayout>Home</BlogLayout>
}

export default HomePage
// web/src/pages/AboutPage/AboutPage.js

import { Link, routes } from '@redwoodjs/router'
import BlogLayout from 'src/layouts/BlogLayout'

const AboutPage = () => {
  return (
    <BlogLayout>
      <p>
        This site was created to demonstrate my mastery of Redwood: Look on my
        works, ye mighty, and despair!
      </p>
      <Link to={routes.home()}>Return home</Link>
    </BlogLayout>
  )
}

export default AboutPage

Notice that the import statement uses src/layouts/BlogLayout and not ../src/layouts/BlogLayout or ./src/layouts/BlogLayout. Being able to use just src is a convenience feature provided by Redwood: src is an alias to the src path in the current workspace. So if you're working in web then src points to web/src and in api it points to api/src.

Back to the browser and you should see...nothing different. But that's good, it means our layout is working.

Why are things named the way they are?

You may have noticed some duplication in Redwood's file names. Pages live in a directory called /pages and also contain Page in their name. Same with Layouts. What's the deal?

When you have dozens of files open in your editor it's easy to get lost, especially when you have several files with names that are similar or even the same (they happen to be in different directories). We've found that the extra duplication in the names of files is worth the productivity benefit when scanning through your open tabs.

If you're using the React Developer Tools plugin this also helps disambiguate when browsing through your component stack:

Back Home Again

One more <Link>, let's have the title/logo link back to the homepage as per usual:

// web/src/layouts/BlogLayout/BlogLayout.js

import { Link, routes } from '@redwoodjs/router'

const BlogLayout = ({ children }) => {
  return (
    <>
      <header>
        <h1>
          <Link to={routes.home()}>Redwood Blog</Link>
        </h1>
        <nav>
          <ul>
            <li>
              <Link to={routes.about()}>About</Link>
            </li>
          </ul>
        </nav>
      </header>
      <main>{children}</main>
    </>
  )
}

export default BlogLayout

And then we can remove the extra "Return to Home" link (and Link/routes import) that we had on the About page:

// web/src/pages/AboutPage/AboutPage.js

import BlogLayout from 'src/layouts/BlogLayout'

const AboutPage = () => {
  return (
    <BlogLayout>
      <p>
        This site was created to demonstrate my mastery of Redwood: Look on my
        works, ye mighty, and despair!
      </p>
    </BlogLayout>
  )
}

export default AboutPage