Nanoc 4 upgrade guide
Nanoc 4 takes a clean break from the past, removing anything that was holding back future development.
The good news is that Nanoc 4.0 is similar to 3.8. Upgrading a Nanoc 3.x site to Nanoc 4.0 only takes minutes.
Why upgrade?
- Nanoc 4 brings identifiers with extensions, and thereby solves a long-standing usability issue. It also introduces glob patterns, which makes rules easier to write.
- Nanoc 4 paves the way for new features and performance improvements. Nanoc 3 exposed its internals in a public API, making it hard to make significant changes.
- Nanoc 3 is in maintenance mode, which means it will only get critical bug fixes.
Installing Nanoc 4
Before installing, ensure you have a supported version of Ruby. Nanoc supports Ruby 2.5 and up:
% ruby --version
ruby 3.4.5 (2025-07-16 revision 20cda200d3) +PRISM [x86_64-linux]
%
To upgrade Ruby, follow the installation instructions on the Ruby website.
You can install Nanoc 4 using RubyGems:
% gem install nanoc
We recommend using Bundler to manage dependencies. When using Bundler, ensure there is a line for Nanoc in the Gemfile that looks like this:
gem 'nanoc', '~> 4.0'
Quick upgrade guide
The following steps will get a Nanoc 3 site working on Nanoc 4 with a minimal amount of changes.
- Change mentions of
Nanoc3toNanoc. - Change mentions of
@site.configto@config. - Add
identifier_type: legacyto the individual data source configurations. For example:data_sources: - type: filesystemdata_sources: - type: filesystem identifier_type: legacy - Add
string_pattern_type: legacyto the configuration file. For example:data_sources: - type: filesystem identifier_type: legacystring_pattern_type: legacy data_sources: - type: filesystem identifier_type: legacy - In Rules, remove the
rep.prefix fromfilter,layout, andsnapshot. For example:compile '*' do rep.filter :erb rep.layout 'default' endcompile '*' do filter :erb layout 'default' end - In the
preprocessblock, use@items.createrather than instantiatingNanoc::Item. For example:@items << Nanoc::Item.new('Hello', {}, '/hello/')@items.create('Hello', {}, '/hello/') - In data sources, use
new_item()ornew_layout()rather than instantiatingNanoc::ItemorNanoc::Layout. For example:def items [Nanoc::Item.new('Hello', {}, '/hello/')] enddef items [new_item('Hello', {}, '/hello/')] end - In data sources, replace the
identifierargument in calls tonew_item()andnew_layout()with an explicitNanoc::Identifierinstance, constructed withtype: legacy:def items [new_item('Hello', {}, '/hello/')] enddef items [new_item('Hello', {}, Nanoc::Identifier.new('/hello/', type: :legacy))] end - Replace
.reps[0]by.reps[:default]. For example:item.reps[0].pathitem.reps[:default].path - Replace calls to
rep_named()byreps[something], where something is the argument torep_named(). For example:item.rep_named(:raw).pathitem.reps[:raw].path - If you use the static data source, disable it for now and follow the extended upgrade instructions below.
Extended upgrade guide
This section describes how to upgrade a site to identifiers with extensions and glob patterns. For details, see the Identifiers and patterns page.
This section assumes you have already upgraded the site following the instructions in the Quick upgrade guide section above.
Before you start, add enable_output_diff: true to the configuration file. This will let the compile command write out a diff with the changes to the compiled output. This diff will allow you to verify that no unexpected changes occur.
html5small, we recommend turning it off before upgrading the site, so that the output diff becomes easier to read.Enabling glob patterns
Before enabling them, ensure you are familiar with glob patterns. For details, see the Glob patterns section on the Identifiers and patterns page.
To use glob patterns:
- Set
string_pattern_typetoglobin the configuration file. For example:string_pattern_type: legacystring_pattern_type: glob - Ensure that all string patterns in the Rules file, as well as in calls to
@items[…],@layouts[…], andrender()throughout the site, start and end with a slash. This is an intermediate step. For example:# Before compile 'articles/*' do layout 'default' end# After compile '/articles/*/' do layout '/default/' end# Before @items['foo'] @layouts['/bar']# After @items['/foo/'] @layouts['/bar/']<!-- Before --> <%= render 'header' %><!-- After --> <%= render '/header/' %> - Replace
*and+with**/*in all string patterns in the Rules file, as well as in calls to@items[…],@layouts[…], andrender()throughout the site. For example:compile '/articles/*/' do layout '/default/' endcompile '/articles/**/*/' do layout '/default/' end@items['/articles/*/']@items['/articles/**/*/']
This approach should work out of the box: Nanoc should not raise errors and the output diff should be empty.
Enabling identifiers with extensions
Before enabling them, ensure you are familiar with identifiers with extensions. See the Identifiers section on the Identifiers and patterns page for documentation.
To use identifiers with extensions:
- Set
identifier_typetofullin the configuration file. For example:identifier_type: legacyidentifier_type: full - Remove the trailing slash from any argument to
compile(),route(), andlayout()in the Rules file, as well as in calls to@items[…],@layouts[…], andrender()throughout the site. If the pattern does not end with a “*”, add “.*”. For example:compile '/articles/**/*/' do filter :kramdown layout '/default/' end compile '/about/' do layout '/default/' endcompile '/articles/**/*' do filter :kramdown layout '/default.*' end compile '/about.*' do layout '/default.*' end@items['/about/'] @layouts['/default/']@items['/about.*'] @layouts['/default.*']<%= render '/root/' %><%= render '/root.*' %> - Update the routing rules to output the correct path. For example:
route '/articles/*/' do # /articles/foo/ gets written to /articles/foo/index.html item.identifier + 'index.html' endroute '/articles/**/*' do # /articles/foo.md gets written to /articles/foo/index.html item.identifier.without_ext + '/index.html' end - Create a routing rule that matches index files in the content directory (such as content/index.md or content/blog/index.md). For example, put the following before any rules matching
/**/*:route '/**/index.*' do # /projects/index.md gets written to /projects/index.html item.identifier.without_ext + '.html' end - If the site has calls to
children(), ensure the lib/helpers.rb file containsuse_helper Nanoc::Helpers::ChildParent, and replace method calls tochildren()with a function call tochildren_of(), passing in the item as an argument. For example:@items['/articles/'].children @item.childrenchildren_of(@items['/articles/']) children_of(@item) - If the site has calls to
parent(), ensure the lib/helpers.rb file containsuse_helper Nanoc::Helpers::ChildParent, and replace method calls toparent()with a function call toparent_of(), passing in the item as an argument. For example:@item.parentparent_of(@item)
Upgrading from the static data source
The static data source no longer exists in Nanoc 4. It existed in Nanoc 3 to work around the problem of identifiers not including the file extension, which is no longer the case in Nanoc 4.
Theoretically, with identifiers with extensions enabled, it is possible to move the contents of the static/ directory into content/. This can be tricky, however, because some rules that did not match any items in static/ might now match.
Because of this, the recommended approach for upgrading is to keep the static/ directory, and set up a new data source that reads from this directory.
In the site configuration, re-enable the static data source, change its type to filesystem, set content_dir to "static" and layouts_dir to null:
data_sources:
-
type: filesystem
-
type: filesystem
items_root: /static
content_dir: 'static'
layouts_dir: null
The null value for the layouts_dir option prevents this data source from loading layouts—the other data source already does so.
Lastly, update the rules to copy these items as-is, but without the /static prefix:
compile '/static/**/*' do
end
route '/static/**/*' do
# /static/foo.html → /foo.html
item.identifier.to_s.sub(/\A\/static/, '')
end
This approach should work out of the box: Nanoc should not raise errors and the output diff should be empty.
A final improvement would be to move the contents of the static/ directory into content/. The main thing to watch out for with this approach is rules that accidentally match the wrong items.
Troubleshooting
If you use Nanoc with a Gemfile, ensure you call Nanoc as bundle exec nanoc. Nanoc no longer attempts to load the Gemfile.
-
If you get a
NoMethodErrorerror onNanoc::Identifier, call.to_son the identifier before doing anything with it. In Nanoc 4.x, identifiers have their own class and are no longer strings.item.identifier[7..-2]item.identifier.to_s[7..-2] If you get a
NoMethodErrorthat you did not expect, you might be using a private API that is no longer present in Nanoc 4.0. In case of doubt, ask for help on GitHub discussions or the Google group.
Removed features
The watch and autocompile commands have been removed. Both were deprecated in Nanoc 3.6. Use guard-nanoc instead.
Because Nanoc’s focus is now more clearly on compiling content rather than managing it, the following features have been removed:
- the
create-itemandcreate-layoutcommands - the
updateandsynccommands - VCS integration (along with
Nanoc::Extra::VCS) - the
DataSource#create_itemandDataSource#create_layoutmethods.