Getting Started: Working With Templates

This is part 2 of the 3-part Getting Started Guide. This page shows you how to create templates to customise how your content is displayed.

Part 1 covers working with content and part 3 covers working with assets.


  1. Overview
  2. JSX templates
  3. JSX components
  4. Import tools and utilities
  5. Vanilla JS templates
  6. Vanilla JS components

Overview

Templates are what JS.SSG uses to display content. They turn the raw content from Mardown files into a defined HTML structure.

Templates in JS.SSG are nothing more than JavaScript functions that return a string of HTML. The content of the page is passed into the function and the template author is then free to do whatever they like with that content. By design, JS.SSG templates are open-ended and flexible. The "happy path" for JS.SSG is to use JSX to create templates and components, but you can use vanilla JavaScript templates if you prefer.

What is JSX?

JSX is an extension to JavaScript that allows developers to write JS code with HTML-like syntax, making it easier to write and maintain complex UIs. JSX code is compiled into regular JavaScript that can be run in a web browser or other JavaScript runtime environment. This allows developers to use the power and flexibility of JavaScript to create interactive UIs while still using familiar HTML-like syntax to define their structure.

JSX templates

Create a template file

To apply a custom template to one of your Markdown pages, create a file called Post.jsx in a folder called templates/ in the root of your project.

├── content/ <--- markdown files go in here
├── templates/
|   └── Post.jsx <--- the new template file
└── package.json

Write a template

In the new Post.js file, write your template function and export it as the default export.

All templates are initialised with three paramaters:

  1. content - the content of the page as a string of HTML
  2. page - an object containing all the frontmatter from the Markdown file
  3. site - an object containing metadata about the site as a whole (root url, site title, etc.)
const Post = ({ content, page, site }) => {
    return (
        <html lang="en">
            <body>
                <h1>{page.title}</h1>
                <div dangerouslySetInnerHTML={{ __html: content }} />
                <a href={site.url}>Home</a>
            </body>
        </html>
    );
};

export default Post;

Why do we need to use dangerouslySetInnerHTML?

The page's markdown has already been converted to HTML before being passed as content to the template, so wrapping it in dangerouslySetInnerHTML ensures that HTML is rendered correctly in the final file

Apply the template to a page

To apply the new template file to a page, we'll use the layout frontmatter item. Templates are identified within JS.SSG by their filename, so if your template file is Post.jsx then we set the page's layout to be "Post" (note: capitalisation is important).

---
title: My First Blog Post
layout: Post
---

Welcome to my first blog post! This is a placeholder post that...

Now when you run yarn jsssg --serve the Post template will be applied to that page.

Do we call them Layouts or Templates?

The terms "layout" and "template" can often seem interchangeable, and in many ways they are. "Layout" refers to the overall structure and design of a web page, while "template" refers to a specific, reusable file that defines the structure of a particular section of the layout. In other words, a layout defines the overall appearance of a web page, while a template defines the structure of a specific part of the page. So we write template files, but apply a layout to a markdown file.

JSX Components

One of the primary benefits of using any templating framework is the ability to split up your code for ease of reuse. After all, you don't want to have to re-write your header and footer for every page. Components are great way to acheive this, and JSX component specifically have the added benefit of being useable within MDX files too!

Components are just small templates

Alternatively, you could say that templates are just large components 😉

Creating a component is exactly the same process as creating a template. Write a .jsx file for your component and export a default function that returns some JSX.

├── templates/
|   ├── Header.jsx
|   └── Post.jsx
// Header.jsx
const Header = ({ url, title }) => {
    return (
        <header>
            <h2>
                <a href={url}>{title}</a>
            </h2>
        </header>
    );
};

export default Header;
// Post.jsx
import Header from "./Header.js";

const Post = ({ content, page, site }) => {
    return (
        <html lang="en">
            <body>
                <Header url={site.url} title={site.title} />
                <h1>{page.title}</h1>
                <div dangerouslySetInnerHTML={{ __html: content }} />
            </body>
        </html>
    );
};

export default Post;

Things to bear in mind when writing JSX templates

There are a few small differences to remember when writing JSX templates instead of vanilla JS ones:

No <!DOCTYPE html>.

By default JSX makes adding a DOCTYPE a bit tricky, so JS.SSG adds that for you automatically. You don't need to include it in JSX templates.

Write components with .jsx file extension, but import them with .js.

Due to the way JS.SSG transpiles components, we need to import them as .js files even though they are written as .jsx:

If your templates file looks like this:

├── templates/
|   ├── Header.jsx
|   └── Post.jsx

You need to import Header like this:

// Post.jsx
import Header from "./Header.js";

const Post = ({ content, page, site }) => {
    return (
        <html lang="en">
            <body>
                <Header />
                // the rest of your template...
            </body>
        </html>
    );
};

Components are simpler with JSX

You can write vanilla JS components, but things look much cleaner and simpler in JSX. If you like to structure your HTML with components, then JSX Templates are the recommended method for doing so.

Import tools and utilities

You don't just need to import components; you can import other things too. Because JS.SSG's templates and components are all "just JS", you can do anything in them that you can in any JS file.

Convert raw Markdown into HTML with the built-in markdown function

While the main content of your page is converted from Markdown to HTML automatically, you may want to convert other Markdown strings into HTML (perhaps you've stored an excerpt in the page's frontmatter?). JS.SSG provides a markdown function that you can use within your templates. Just import it like so:

// PostHeader.jsx
import { markdown } from "jsssg";

const PostHeader = ({ title, excerpt }) => (
    <header>
        <h1>{title}</h1>
        <div dangerouslySetInnerHTML={{ __html: markdown(excerpt) }} />
    </header>
);

export default PostHeader;

Write utility functions to transform your data

For example, you could import a function to parse yyyy-mm-dd formatted dates into a different format:

├── utils/
|   ├── dates.js
├── templates/
|   └── Post.js
// dates.js
import moment from "moment";

export const formatDate = date => moment(date, "YYYY-MM-DD").format("MMM Y");
// PostHeader.jsx
import { formatDate } from "../utils/dates.js";

const PostHeader = ({ title, date }) => (
    <header>
        <h1>{title}</h1>
        <p>
            <span>Published on</span>
            <time dateTime={date} itemProp="datePublished">
                {formatDate(date)}
            </time>
        </p>
    </header>
);

export default PostHeader;

Vanilla JS templates

Writing your templates in vanilla JavaScript is almost exactly the same as writing a JSX template, except you save the file with the .js extension.

const Post = ({ content, page, site }) => {
    return `<!DOCTYPE html>
            <html lang="en">
                <body>
                    <h1>${page.title}</h1>
                    ${content}
                    <a href="${site.url}">Home</a>
                </body>
            </html>`;
};

export default Post;

Vanilla JS components

As with JSX/JS templates, vanilla components are very similar to JSX ones. The crucial difference is in how the components are integrated into their parent template: we need to call JS component functions directly (Header()), rather than using them as JSX tags (<Header />).

├── templates/
|   ├── Header.jsx
|   └── Post.jsx
// Header.js
const Header = ({ url, title }) => {
    return `<header>
                <h2>
                    <a href="${url}">${title}</a>
                </h2>
            </header>`);
};

export default Header;
// Post.js
import Header from "./Header.js";

const Post = ({ content, page, site }) => {
    return `<!DOCTYPE html>
            <html lang="en">
                <body>
                    ${Header({ url: site.url, title: site.title })}
                    <h1>${page.title}</h1>
                    ${content}
                    <a href="${site.url}">Home</a>
                </body>
            </html>`;
};

export default Post;

Don't mix JS and JSX templates

Mixing JS and JSX formats can get confusing very quickly. It is stongly recommended that you only use one type of template per layout. A layout can be contructed from multiple templates and/or components, so feel free to have JS templates for one type of page (they're great for xml pages, for instance)


Next steps

Now you know how to create templates for JS.SSG, the next step is to add styles and other assets to those templates.