Some of my Jekyll posts are private and should only be visible when running locally or on a private server. Here's how I did it.
I store my private posts in a _private/_posts
directory in the root of the
Jekyll project and use a custom Jekyll Hook
plugin stored at
_plugins/private-posts.rb
to decide if the posts should be included in the
build or not. An environment variable named PRIVATE
is passed into the serve
command when you want the private posts to be displayed eg PRIVATE=true bundle
exec jekyll serve
.
# Private Posts
# -------------
# _plugins/private-posts.rb
# Allows for posts stored within the `_private` directory to be published.
# Enable this feature by passing the `PRIVATE` environment variable in when
# serving eg `PRIVATE=true bundle exec jekyll serve`
Jekyll::Hooks.register :site, :pre_render do | site |
if ENV["PRIVATE"] == "true"
site.reader.retrieve_posts("#{site.source}/_private")
Jekyll.logger.info "Private posts:", "enabled".red
else
Jekyll.logger.info "Private posts:", "disabled. Enable with PRIVATE=true"
end
end
# Enable private posts
# (all posts in _private/_posts will be published)
roast@planetroast:~$ PRIVATE=true bundle exec jekyll serve
Configuration file: /home/code/planetroast.com/_config.yml
Source: /home/code/planetroast.com
Destination: /home/code/planetroast.com/_site
Incremental build: disabled. Enable with --incremental
Generating...
Private posts: enabled
done in 0.913 seconds.
Auto-regeneration: enabled for '/home/code/planetroast.com'
Server address: http://127.0.0.1:4000
Server running... press ctrl-c to stop.
# Regular serve command
# (private posts will be hidden)
roast@planetroast:~$ bundle exec jekyll serve
Configuration file: /home/code/planetroast.com/_config.yml
Source: /home/code/planetroast.com
Destination: /home/code/planetroast.com/_site
Incremental build: disabled. Enable with --incremental
Generating...
Private posts: disabled. Enable with PRIVATE=true
done in 0.844 seconds.
Auto-regeneration: enabled for '/home/code/planetroast.com'
Server address: http://127.0.0.1:4000
Server running... press ctrl-c to stop.
If you have any drafts which are private you can store them in
_private/_drafts/
and they will work just the same as regular drafts:
# Only show 'regular' drafts stored in the main _drafts folder
bundle exec jekyll serve --draft
# Show all drafts including those in _private/_drafts
PRIVATE=true bundle exec jekyll serve --draft
If you like to organise your Jekyll posts into
collections and have set a custom
collections_dir
in your _config
then the approach is different because the
posts will be assigned to the ‘private’ collection instead of Jekyll’s built in
‘posts’ collection.
This means that the private posts won’t be rendered by default but we can easily
fix that by modifying the site config via our plugin. It also means that the
private posts won’t appear alongside your regular posts which we can fix by
appending them to the site.posts.docs
array.
# Private Posts
# -------------
# _plugins/private-posts.rb
# Add the private collection into the site config. We must set the `output`
# attribute otherwise the posts won't be rendered when the site builds. The
# permalink attribute is optional, by default it will be `/private/:slug`.
Jekyll::Hooks.register :site, :after_init do | site |
if ENV["PRIVATE"] == "true"
site.config['collections']['private'] = {
"output" => true,
"permalink" => "/:slug"
}
Jekyll.logger.info "Private posts:", "enabled".red
else
Jekyll.logger.info "Private posts:", "disabled. Enable with PRIVATE=true"
end
end
# Add all the posts in the private collection into the main `_posts` collection.
# This step is necessary if you want your private posts to be treated as regular
# posts instead of posts within the private collection.
Jekyll::Hooks.register :site, :pre_render do | site |
if ENV["PRIVATE"] == "true"
site.collections["private"].docs.each do | private_post |
private_post["categories"] << "private" # Add category (optional)
site.posts.docs << private_post
end
end
end
I’m not sure why we can’t use the site.reader.retreive_posts
method in this
case instead of manually adding each private to the site.posts
array? The
source code shows that it only reads posts and drafts from the
custom_collections_dir
,
but as our _private
directory is in the collections
directory I would expect
this to work. Not a huge deal in my case as I’d need to iterate through each
private post anyway to add categories and front matter.
These are all potential solutions for making private posts in Jekyll which for one reason or another I rejected.
Jekyll has a built in system for managing drafts where drafts skip the build
process and don’t appear on the site, so you could simply set all your private
posts to drafts. The problem with this method is that in order to set a post as
a draft you have to either omit the date or store it in the _drafts
directory. Both of these are deal breakers for me because I want the dates on
my posts and I certainly don’t want to mix up private posts with public posts.
Why not use the built in published: false
option in the front matter of your
private posts? Then use the build command option --unpublished
to show the
private posts when you need them? Because it’s too easy to forget to set a
post to unpublished and accidentally publish it, that’s why.
Setting a custom front matter of private: true
on each private post is a
possible option which could be combined with a custom pre render
hook to
prevent the private posts from being displayed, but it suffers from the same
problem as above where it’s too easy to forget and accidentally publish a
private post.
Update: Although I initially dismissed this solution as an unnecessary complication, since reorganising more of my data into collections and using a custom collections directory I’ve changed my opinion on this strategy and it’s now my preferred approach. The method I used is defined above.
Creating a custom collection
for private posts might be an option as the
publishing of the posts can be controlled via the output
option in the
_config
file. But this is far from ideal because Jekyll won’t consider the
files within the collection as posts
(they’ll be private_posts
or
whatever you choose to name them) meaning you’ll have to merge them into the
posts array or mess about with your templates to get them working. Plus
you’ll need to edit the config each time you switch on private posts which
is a pain and poses a risk of accidentally commiting the config with the
private posts enabled.
Jekyll 2.5 does allow for custom subcommands via plugins would would be great as it will be added to the existing list of Jekyll commands. However, I felt it was overkill to create and maintain a Gem just for this use case. Something told me that there was an simpler way to achieve private posts.