Manage a static site blog with 11ty

If you’re looking to build a simple, static website, we think 11ty is an awesome solution. This guide will help you get started with 11ty and using Portway as a headless CMS.

Update One of our customers, Ashur, created an 11ty Portway Starter which we recommend checking out. It will get you started with a nice base theme, and some more options. If you’d like to get a bare bones 11ty project going, then continue on with our guide.

Installing 11ty

First, let's create a directory, initialize the project, and install 11ty.

mkdir 11ty-example
cd 11ty-example
npm init -y
npm install --save-dev @11ty/eleventy node-fetch dotenv outdent

You now have 11ty installed and should be able to run npx eleventy to make sure. If all is well, you should see the following:

➜  11ty-example npx eleventy
Wrote 0 files in 0.17 seconds (v0.11.1)

A basic 11ty setup

First, we're going to add some configuration and then add a basic layout for all of your pages to use. By default, 11ty likes to put everything in an directory named _includes, but we like to be more specific.

  1. Create a file .eleventy.js (note the preceeding dot) at the root of your project. Add this tiny configuration to this file and save it:
require('dotenv').config()
const markdownIt = require("markdown-it")
const outdent = require('outdent')

module.exports = function (eleventyConfig) {
  const md = new markdownIt()

  eleventyConfig.addPairedShortcode("markdown", (content) => {
    return md.render(outdent`${content}`)
  })

  return {
    dir: {
      includes: "_includes",
      layouts: "_layouts"
    }
  }
}
  1. Next, create a directory _layouts and add the following file to it, named default.njk:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
  </head>
  <body>
    <div>
      <header>
        <h1><a href="/">My blog with Portway</a></h1>
      </header>
      <main>
        {{ content | safe }}
      </main>
    </div>
  </body>
</html>
  1. Finally, let’s add your homepage by creating a file named index.md at the root of your project. In that file:
---
layout: default.njk
title: Home
---

I am some homepage content
  1. Now that you have the most basic setup, run the 11ty server and see your homepage!
npx eleventy --serve

Next up, Portway

We’re going to set up a Portway project to use for your blog.

  1. Create a new Portway project.
  2. Note the ID of the project in the URL, in my case it is 39 /d/project/39.
  3. Open your project’s settings, and navigate to API keys.
  4. Add a project key named "11ty", with "Reader" permissions.

Create some documents

In your new Portway project, create a few documents and publish them. These will become blog entries in your 11ty site.

Create the env file

We’re going to create an env file to store your project ID, and your reader key so that 11ty can communicate with Portway.

  1. Create a file named .env at the root of your 11ty project, adding your project ID and reader key like so:
PORTWAY_PROJECT=39
PORTWAY_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Niwib3JnSWQiOjEsImlhdCI6MTYxMjk4NDUyMiwiaXNzIjoiYm9ua2V5Ym9uZyJ9.Y9FutAJtYYIVrhSBL01eF817eUOlGzUunqdonYjSz4Y
  1. Create a (or modify an existing) file named .gitignore and add .env in there. This will ensure you don’t accidentally upload your key anywhere. Make sure you have node_modules in the .gitignore as well, as this is an issue with 11ty.
.env
node_modules

Hook Portway to 11ty

Now let’s connect Portway. At the root of your 11ty project, create a directory named _data and inside of it, create a file named portway.js.

Since you can export data to 11ty from javascript files, we’re going to fetch the documents in your new Portway project and pass them along to 11ty in this new file. You could connect to other services in a similar way by adding another javascript file to the _data directory and returning an array of data.

  1. Edit portway.js and add the following:
const fetch = require('node-fetch')
const portwayProjectId = process.env.PORTWAY_PROJECT
const portwayKey = process.env.PORTWAY_KEY

const fetchFromPortway = async (url) => {
  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${portwayKey}`,
    }
  })
  if (response.ok) {
    return response.json()
  } else {
    throw new Error(response.statusText)
  }
}

const fetchProjectDocuments = async (projectId) => {
  let documents
  try {
    const { data } = await fetchFromPortway(`https://api.portway.app/api/v1/projects/${projectId}/documents`)
    documents = data
  } catch (err) {
    throw new Error(`Unable to fetch documents for project with id ${projectId}`)
  }
  return documents
}

const fetchDocumentFields = async (documentId) => {
  let fields
  try {
    const { data } = await fetchFromPortway(`https://api.portway.app/api/v1/documents/${documentId}/fields`)
    fields = data
  } catch(err) {
    throw new Error(`Unable to fetch fields for document with id ${documentId}`)
  }
  return fields
}

module.exports = async function() {
  let documents = []
  const projectDocuments = await fetchProjectDocuments(portwayProjectId)
  await Promise.all(
    projectDocuments.map(async (document) => {
      const documentFields = await fetchDocumentFields(document.id)
      const post = {
        ...document,
        content: documentFields,
        fields: {
          date: document.createdAt,
          url: `/blog/${document.slug}`
        }
      }
      documents.push(post)
    })
  )
  return documents
}
  1. Now we’ll create a blog template that takes your published Portway documents and serves them at /blog/document-name. Create a file at the root of your 11ty project named blog.njk and add the following:
---
pagination:
    data: portway
    size: 1
    alias: document
layout: default.njk
permalink: "blog/{{ document.slug }}/"
eleventyComputed:
    title: "{{ document.name }}"
---

{% set portway_string = 1 %}
{% set portway_text = 2 %}
{% set portway_number = 3 %}
{% set portway_image = 4 %}
{% set portway_date = 5 %}
{% set portway_file = 6 %}

{% for field in document.content %}
    {% if field.type == portway_string %}
        <div class="string">{{ field.value }}</div>
    {% elif field.type == portway_text %}
        <div class="markdown">
        {% markdown %}{{ field.value }}{% endmarkdown %}
        </div>
    {% elif field.type == portway_number %}
        <div class="number">{{ field.value }}</div>
    {% elif field.type == portway_image %}
        <div class="image">
            <img src="{{ field.value }}" />
        </div>
    {% elif field.type == portway_date %}
        <div class="date">{{ field.value }}</div>
    {% elif field.type == portway_file %}
        <div class="file">{{ field.value }}</div>
    {% endif %}
{% endfor %}

<nav>
    <ol>
        <li>{% if pagination.href.previous %}<a href="{{ pagination.href.previous }}">Previous</a>{% else %}Previous{% endif %}</li>
        {%- for pageEntry in pagination.pages %}
        <li><a href="{{ pagination.hrefs[ loop.index0 ] }}"{% if page.url == pagination.hrefs[ loop.index0 ] %} aria-current="page"{% endif %}>Page {{ loop.index }}</a></li>
        {%- endfor %}
        <li>{% if pagination.href.next %}<a href="{{ pagination.href.next }}">Next</a>{% else %}Next{% endif %}</li>
    </ol>
</nav>
  1. Let’s also add links to the homepage to these blog posts. Update index.md:
---
layout: default.njk
title: Home
---

I am some homepage content

<nav>
<ul>
  {% for document in portway %}
  <li>
    <a href="{{ document.fields.url }}">{{ document.name }}</a>
  </li>
  {% endfor %}
</ul>
</nav>

And that’s it! You’ve got the beginnings of your very own 11ty blog, using Portway as your content source. A good next step would be cleaning up the layout and templates using includes as well as hooking up automatic deployments with Portway’s webhooks.

Let us know what you think!