Basics
Features
Best Practices
Addon Development
Related Docs
Dark Mode

#Pages and Layouts

Pages are the main construct in MercuryCMS. Each page has its own markup, style, and sometimes script.

#Making a Simple Page

To make a simple page, go to Pages in the Nav Bar and click "New Page". Give it a name, like "About Us" and a path like "/about-us". In the markup tab, you can write:

h1 About Us
p This is the About Us page!

This will create the following page:

public/
  about-us/
    index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>About Us</title>
        <meta property="og:title" content="About Us">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/about-us">
        <meta property="og:description" content="About Us">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="About Us">
    </head>
    <body>
        <h1>About Us</h1>
        <p>This is the About Us page!</p>
    </body>
</html>

#Markup

Markup for the page is written in the Markup tab. You can choose to write in either HTML or Pug, though Pug is highly recommended for its templating capabilities. Each individual Page can be either, so you don't need to choose one language for the whole site.

See Templating for examples of what special things you can do within Page, Layout, and Component markup.

#Style

Style is written in the Style tab. Similarly to the Page's markup, you can write in either CSS or Stylus, though Stylus is highly recommended for its ease of maintainability.

You should read Markup and Style to make your Page's markup and style extremely easy to read and maintain.

When you add style to a page, it will automatically create a file called style.css in the same directory as the page's index.html and add a <link rel="stylesheet" href="style.css"> tag to the <head>.

Each page gets its own CSS file rather than having a single, large global CSS file. This means pages will load extremely fast as only the CSS that's actually needed is loaded with the Page.

#Script

You can add JavaScript to the page within the Script tab. Most Pages will not need JavaScript at all, and the use of JQuery to modify the page on the client is not recommended as this makes the Page extremely hard to maintain, slow, and generally produces a terrible experience for users, designers, and developers.

It is very rare that you'll need to write JavaScript for a specific Page or Layout. If you want to make something like a hamburger menu, navigable tabs, or provide other user interactivity, you will typically want to encapsulate that within a Component and use that on the Page.

Most tasks require simple, standard JavaScript, though more complex interactivity that requires large updates to the page or API calls will typically require the use of a Vue Component.

If you do add script to the Page, note that the Site script, Layout script, Page script, and any present Components' scripts will be combined into a single script.js file stored alongside the Page's index.html and style.css files. All scripts will be run asynchronously.

To run synchronous scripts, you will need to add a Tag to the Site, Layout, or Page with a <script> tag in it, which will run before the Page's combined asynchronous scripts. This can be useful for things like declaring variables on the window object or otherwise setting things up that the later scripts need.

You can use top-level await within any script.

#Layouts

Layouts allow you to create shared, reusable layouts for your Page's content. This means you can update many Pages at once just by updating the Layout they use. You can have as many Layouts as you want, though most websites only need one or a few.

The most common use for Layouts is adding a header and footer to Pages. Make a new Layout and call it "Main Layout" then add the following markup:

doctype html
html
  head
    meta(charset="utf-8")
    title #{ page.title }
  body
    .site-header
      p This is the header!
    .site-content !{ page.body }
    .site-footer
      p This is the footer!

Now you can go to any Page and select "Main Layout" from the Layout dropdown to use it. You'll see that the Page's content was templated into the Layout where !{ page.body } was used:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>About Us</title>
        <meta property="og:title" content="About Us">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/about-us">
        <meta property="og:description" content="About Us">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="About Us">
    </head>
    <body>
        <div class="site-header">
            <p>This is the header!</p>
        </div>
        <div class="site-content">
            <h1>About Us</h1>
            <p>This is the About Us page!</p>
        </div>
        <div class="site-footer">
            <p>This is the footer!</p>
        </div>
    </body>
</html>

The Layout's style will be merged with the Page's style into a single style.css file alongside the combined index.html file.

#Page Data

You can add data to a page using the Data tab and the add() function. Any values you put into the object will then be available within the Page's title, path, markup, style, script, and SEO tags.

add({
  fruits: [
    {name: 'apple', color: 'red'},
    {name: 'pear', color: 'green'},
    {name: 'orange', color: 'orange'},
    {name: 'banana', color: 'yellow'},
  ],
})
.fruit-page
  p There are many kinds of fruit. Here are some of my favorites:
  ul
    each fruit in fruits
      li I like #{ fruit.color } #{ fruit.name }s
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Fruit</title>
        <meta property="og:title" content="Fruit">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/fruit">
        <meta property="og:description" content="Fruit">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="Fruit">
    </head>
    <body>
        <div class="fruit-page">
            <p>There are many kinds of fruit. Here are some of my favorites:</p>
            <ul>
                <li>I like red apples</li>
                <li>I like green pears</li>
                <li>I like orange oranges</li>
                <li>I like yellow bananas</li>
            </ul>
        </div>
    </body>
</html>

#Dynamic Paths

Suppose you have a Blog Post Collection that has all of the Site's post content and you want to make a Page for each of them. We need some kind of path like /posts/{{ blogPost.slug }}.

That's exactly how you do it. MercuryCMS knows you have a Collection called "Blog Post", so if you put /posts/{{ blogPost.slug }} in the Page's path, it knows you want to have one page for every item in the Blog Post collection, and it will create a single blogPost object per Page you can use in the markup, style, and script for the Page.

You can then use blogPost in the Page's markup like this:

.blog-post-page
  h1 #{ blogPost.title }
  .author Written by #{ blogPost.author }
  .blog-content !{ blogPost.bodyHtml }

#Advanced Dynamic Paths

You may want to make more advanced dynamic routes. For example, say you wanted to make a route like /authors/{{ author.slug }}/books/{{ book.slug }}/pages/{{ bookPage.number }}.

This is a bit too complex for the CMS to figure out. It knows you have an "Author", "Book", and "Book Page" Collection, but it doesn't necessarily know you want to go through each Author and then just that Author's Books, and then just that Book's Book Pages. For this, you need to use the add() function in the Data tab.

Before, we used the add() function a single time to add the data we wanted for the Page, but you can also call it multiple times to add multiple Pages. Let's start with a simple example and then work up to the more complex case above.

Let's start by making a single page for every letter of the alphabet:

let letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
for (let i=0; i<letters.length; i++) {
  let letter = letters[i]
  add({
    letterNumber: i+1,
    letter,
  })
}

Now we'll make the Page title The Letter {{ letter }} and the path will be /letters/{{ letter.toLowerCase() }}

.letter-page
  p This page is for the letter #{ letter }. This letter is number #{ letterNumber } in the alphabet. It looks like "#{ letter }" when it's uppercase and "#{ letter.toLowerCase() }" when it's lowercase.

This will generate 26 Pages. Here's what /letters/a will look like:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>The Letter A</title>
        <meta property="og:title" content="The Letter A">
        <meta property="og:type" content="website">
        <meta property="og:url" content="https://example.com/letters/a">
        <meta property="og:description" content="The Letter A">
        <meta property="og:locale" content="en_US">
        <meta property="og:site_name" content="My Site">
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="description" content="The Letter A">
    </head>
    <body>
        <div class="letter-page">
            <p>This page is for the letter A. This letter is number 1 in the alphabet. It looks like &quot;A&quot;when it&apos;s uppercase and &quot;a&quot;when it&apos;s lowercase.</p>
        </div>
    </body>
</html>

Now that you understand that add() simply adds data that can then be templated into the Page's title, path, markup, style, and script, you can then see how we could make our more complex example above. You'll need to understand how Collections work to fully understand this, but you should be able to follow along anyway if you haven't gotten there yet:

for (let author of authorList) {
  for (let book of author.getBooks()) {
    for (let bookPage of book.getBookPages()) {
      add({
        author,
        book,
        bookPage,
      })
    }
  }
}

Now we can make the markup:

.book-page
  .title #{ book.name } by #{ author.name }
  .page-count Page #{ bookPage.number }/#{ bookPage.totalPages }
  .content !{ bookPage.bodyHtml }
  if (bookPage.number > 1)
    a.page-button(href=`/authors/${author.slug}/books/${book.slug}/pages/${bookPage.number-1}`) Previous Page
  if (bookPage.number < bookPage.totalPages)
    a.page-button(href=`/authors/${author.slug}/books/${book.slug}/pages/${bookPage.number+1}`) Next Page

#Tags

When you're writing a Page, the markup tab contains its body content, which is injected into the <body> tag within the generated HTML file. If you want to add something to the <head>, you'll need to either write it into the Layout used by the Page, or use the Tags system.

See Tags for information on how this works.

#SEO

For information about how a Page's SEO works, see SEO.

#Build

During the Build, you can use the page object to get information about the Page.

NameDescriptionExample
page.titleThe page's titleAbout Us
page.bodyThe page's body as HTML<p>Some Content</p>
page.pathThe page's path/posts/welcome-to-the-site
h1 #{ page.title }
p Thanks for visiting the #{ page.title } page!
a(href=page.path) Reload Page

Note: page.body is only available within the Layout. You cannot use it within the Page itself or any of its Components since it hasn't been determined yet.

To understand the exact Build process steps, how the index.html file is put together, and what variables you have available within the title, path, markup, style, script, data, and SEO fields, you can read the Build System and Templating documentation.

If you're reading the documentation straight through, it's recommended you continue on and dive into those topics in order.

#Page Publishing

There are times you may want to disable a Page. This is useful when importing broken HTML from WordPress, or you may wish to work on a Page in staging but not have it be sent to Production until it's ready. You may also want to temporarily disable Pages to improve Build time.

You can set the "Publish To" option on Pages to publish to Production, be only available on staging, or disable it altogether. Disabled pages will also be hidden from the Sitemap.

#Special Pages

Most Pages you create will be "Normal Pages", but you can also create 404 Not Found Pages, and 500 Server Error Pages. These Pages will show when the user navigates to a path that does not exist.

If you leave the Page's "match" field blank, this will be the default Page of this type. For example, you can have a default 404 Page and a default 500 Page. If you want to have the Page only shown when the user is at a certain path, you can set the "match" to a Regular Expression. If the path the user navigated to matches this Regular Expression, it will show this Page.

For example, you can make a default 404 Page, then make another 404 Page with a match of "^/blog/". This will only match if they go to a path like "/blog/some-post" that doesn't exist. You can then have this 404 Page have custom content that says something like "We couldn't find that post", with a link to the main blog index and links to the most recent posts.

500 Server Error Pages are only used in the Staging Site at this time. These allow you to override the default error Page. If the user is signed in, an err object will be available to template which allows you to access all of the data the user will want to know about the error that occurred.

.my-custom-error-page
  if err
    each item in err.breadcrumbs
      a(href=item.path target="_blank") #{ item.name }
    p #{ err.message }
    pre #{ err.stack }
    pre #{ err.log }
  else
    p There was an error during the build.

#Staging Site Menu

When you create dynamic paths by Templating, the Staging Site will automatically link to the Collection Entry used on that Page. For example, if you make a path like /blog/{{ blogPost.slug }}, an "Edit Blog Post" option will be available when viewing that Page in the Staging Site.

When using add() to make dynamic paths, you have more control over these options. The second argument to add() is an options object where you can specify Collection Entries to make available in the Staging Site menu. For example:

add({
  blogPost,
}, {
  entries: [
    blogPost,
    blogPost.categoryRef,
    blogPost.authorRef,
  ]
})

This would add "Edit Blog Post", "Edit Category", and "Edit Author" to the Staging Site menu. Note that each thing you pass in must be a Collection Entry for this to work.