Use a Trello board as a CMS to create & manage a website.
- Use Trello cards to represent the pages, blog posts and projects on your site
- Pages render the markdown in your card's description
- Setup a blog, which is a Trello list, then quickly add and author posts
- Showcase projects, which is a different Trello list, then filter and view them
- Configurable structure, showing only the pages you want
- Content caching so you don't get rate limited and page loads are fast
- Plugin system for dynamically adding your own page types
- Setup a timeline, which is a Trello list, then add events and milestones
- Know how to use
docker
&docker-compose
- A Trello account and board to author your content
- Understand JSON
- Get a Trello authentication key & token
- Get your
TRELLO_APP_KEY
from trello.com/app-key - Using your key, get your
TRELLO_TOKEN
fromhttps://trello.com/1/authorize?expiration=never&scope=read&response_type=token&name=Husky%20CMS&key=__YOUR_KEY_HERE__
- Make a Trello board for your site's content
- Add 5 lists:
Draft
,Pages
,Blog
,Projects
and get the ids for them- Open a card on the list you want to get the id of
- Add
.json
onto the end of the url & reload - Copy the text and paste it into a JSON formatter
- Find the value for
idList
in the parsed json
- Add 5 lists:
- Create a docker-compose.yml using your auth and the ids of those lists
version: '3'
services:
redis:
image: redis:4-alpine
restart: unless-stopped
husky-site:
image: openlab/husky-cms:latest
restart: unless-stopped
ports:
- 3000:3000
environment:
SITE_NAME: FancySite
OWNER_NAME: AwesomeCompany
OWNER_LINK: link_to_website
REDIS_URL: redis://redis
TRELLO_APP_KEY: your_trello_app_key
TRELLO_TOKEN: your_trello_app_key
PAGE_LIST: list_id_for_pages
PROJECT_LIST: list_id_for_projects
BLOG_LIST: list_id_for_blog_posts
TIMELINE_LIST: list_id_for_timeline
TIMELINE_DATE_ID: custom_field_id
-
Run
docker-compose up -d
-
Visit localhost:3000
-
Add cards to your board and see how the site changes
You can set different combinations of lists.
If you just have PROJECT_LIST
, BLOG_LIST
or TIMELINE_LIST
set, the site will just contain that page.
If you have PAGE_LIST
set, the site will show multiple pages.
Important – When using PAGE_LIST
, Husky uses a card named Home
as the root page of your site.
To replace site name with your brand use CUSTOM_BRAND_URL
to point to image of your logo.
To replace timeline marker with your custom image use CUSTOM_TIMELINE_MARKER_URL
to point to your marker.
Blog, timeline and project pages have extra environment variables to configure their render.
BLOG_SLUG
, TIMELINE_SLUG
& PROJECT_SLUG
are used to determine the url basis for the page and sub pages.
BLOG_NAME
, TIMELINE_NAME
& PROJECT_NAME
is used for the navigation name.
You can set to empty string, ''
, to hide the page from navigation.
BLOG_TITLE
, BLOG_SUBTITLE
, TIMELINE_TITLE
, TIMELINE_SUBTITLE
, PROJECT_TITLE
& PROJECT_SUBTITLE
configure the bulma hero on the page.
Again you can set to an empty string, ''
, to hide the hero.
If you want more that one of a blog, timeline or project page you can set BLOG_LIST
, TIMELINE_LIST
or PROJECT_LIST
to a comma separated list of ids instead of just one.
For example 'FIRST_ID,SECOND_ID,THIRD_ID'
When in multi-page mode, each list id becomes a root-level page with the index added on the end.
For example, /projects_1
, /projects_2
and /projects_1
.
You can combine this with setting PROJECT_NAME
, TIMELINE_NAME
BLOG_NAME
to an empty string ''
,
to hide all the pages from the navigation.
Timeline uses Custom Fields Power-Ups and Date
custom field. If using free version of Trello you can only use ONLY one Power-Up on a board.
Setup timeline
- Enable Custom Fields Power-Up on your board
- Add
New Field
namedDate
with typeDate
- Add card with
Date
toTimeline
list and open it - Add
.json
onto the end of the url & reload - Copy the text and paste it into a JSON formatter
- Find the value for
idCustomField
forDate
in the parsed json
If you want to add milestones to your timeline then add a card with no Date
parameter set. The title
of the card will be rendered as milestone on the timeline.
Husky uses plugins to add different page types, for examples see server/modules. To add your own plugins, mount them in with a docker volume.
volumes:
- ./my_plugin.js:/app/plugins/my_plugin.js
- ./my_template.pug:/app/plugins/templates/my_template.pug
There are two types of plugin, Page plugins & Content plugins.
A page plugin adds a type of page to Husky, optionaly rendered if it's variables are set. It'll appear in the nav along the top, or at the root if just this page's variables are set.
Here's an example page plugin, my_plugin.js
function route(ctx) {
const message = process.env.MESSAGE
ctx.renderPug('my_template', 'My Page', { message })
}
module.exports = function(husky, utils) {
husky.registerPage('my_page', {
name: 'My Page',
templates: ['my_template'],
variables: ['MESSAGE'],
routes: {
'./': route
}
})
}
And its corresponding template, my_template.pug
.hero.is-large.is-primary
.hero-body
.container
h1.title Page says: #{message}
This adds a custom page type, my_page
, which shows when the MESSAGE
environment variable is set. If only the MESSAGE
variable is set, it will be the only page, /
otherwise it will be at /my_page
and appear as My Page
.
The pug template is rendered inside the site skeleton, which has the theme loaded along with the nav bar and footer above and below it.
Your plugin should expose a single function via module.exports
, which takes a husky instance and utils object as parameters.
If you want to serve static files, you can always mount them into /app/static
.
Routing
Husky uses Koa under the hook and modifies its context:
field | type | use |
---|---|---|
ctx.sitemode | string | If the site is serving a specific page or any |
ctx.skipCache | bool | Whether to skip using the trello cache i.e. ?nocache |
ctx.pages | card[] | The page cards from the list with id PAGE_LIST |
ctx.sitetree | node[] | The sitetree nodes for the active pages |
ctx.husky | husky | The husky reference |
It also adds these methods for rendering / errors
ctx.renderPug(template, title, data)
ctx.notFound()
A content plugin lets your customise how a card is rendered into html.
let pageviews = {}
module.exports = function(husky, utils) {
husky.registerContentType('pageviews', {
parser: card => {
if (!pageviews[card.id]) {
pageviews[card.id] = 0
}
return `<p> Page views: ${++pageviews[card.id]}`
},
order: 75
})
}
This registers a plugin which adds the pageviews at the bottom of each page. Here is the available config:
By default each blob is wrapped in a
<div class="content-TYPE">
, whereTYPE
is your plugin name.
field | type | use |
---|---|---|
parser | func | The parser function, takes a card and returns html |
order | number | Where to put this content, 0 being earlier, 100 later |
noWrapper | boolean | If you don't want the content to be wrapped in a div |
Ordering
The ordering numbers are arbitrary and are rendered lowest first.
The card's markdown is rendered at order 50
,
so less than that will be before the markdown
and more than that will be after.
Husky periodically fetches cards from Trello and puts them into redis at a predefined interval.
Internally this uses the husky.fetchCards(listId)
method.
Whenever you call this method, husky will remember your listId
and periodically fetch new cards.
The default interval is 5000 milliseconds but you can override this
by setting the POLL_INTERVAL
environment variable.
Set it to the number of milliseconds you want to wait between fetches.
let cards = await husky.fetchCards('your_list_id')
To develop on this repo you will need to have Docker and node.js installed on your dev machine and have an understanding of them. This guide assumes you have the repo checked out and are on macOS, but equivalent commands are available.
You'll only need to follow this setup once for your dev machine.
# Setup your ENV, using the same environment variables from above
cp .env.example .env
These are the commands you'll regularly run to develop the API, in no particular order.
# Startup a development redis database
# -> Runs on port 6379 on localhosts
docker-compose up -d
# Startup the dev server
# > This will watch for changes in /app and rebuild js/sass assets
# > This will NOT watch js for changes in /server, you need to restart it for that
# > This will recompile .pug templates when NODE_ENV=development is set in your .env
npm run dev
# Deploy docker images
# > Uses REGISTRY file & package version to tag the image and push to dockerhub
# > More info: https://docs.npmjs.com/cli/version
npm version # major | minor | patch
# Stop the development redis database
# -> Do this after you stop development
docker-compose stop
These are commands you might need to run but probably won't, also in no particular order.
# Generate js and css assets using parcel-bundler
# -> Writes them to the dist/ folder
npm run build
# Generate the table of contents for this readme
# -> It'll replace content between the toc-head and toc-tail HTML comments
npm run gen-readme-toc
# Manually lint code with Eslint
npm run lint
# Manually format code
# -> This repo is setup to automatically format code on git-stage
npm run prettier
# Run the application in production
# -> This is the entrypoint in the docker image
# -> It assumes assets are built into dist/
npm run start
The server behaves differently depending on what NODE_ENV
is set
development
Run with
npm run dev
.pug
files will be compiled for each request, so you always get the latest version- Requests to trello will not be cached
.js
&.sass
assets will be hot-reloaded, meaning they will update in the browser on save
production
Run with
npm run start
.pug
files will be compiled once at startup.js
&.css
assets will be optimised and minimised
This repo uses Prettier to automatically format code to a consistent standard. It works using the husky and lint-staged packages to automatically format code whenever code is commited. This means that code that is pushed to the repo is always formatted to a consistent standard.
You can manually run the formatter with npm run prettier
if you want.
Prettier is slightly configured in .prettierrc.yml and also ignores files using .prettierignore.
This repo uses an npm postversion
script to automatically build a version of the docker image whenever the npm version changes.
This is designed to be used with the npm version
command so all docker images are semantically versioned.
If you want to archive a husky-cms site, here's a blog post which shows how you can do that
- Display card's attachments, for example:
- A carousel of images
- A preview of a git repo
- Embedding a youtube video
- Use linked cards to nest pages and create a page hierarchy
- Different page templates
- Commenting / voting using the Trello API
- Use the board's theme to colour the site
- A CLI to ease scraping trello for keys and ids
This repo was setup with robb-j/node-base