How To: Create a Blog
This guide walks you through building a blog with Alchemy. Each blog post is a page with its own URL, giving you a built-in publishing workflow, publication dates, and tagging support. The blog overview page automatically lists all published posts.
You will create two page layouts — one for the overview and one for individual posts — along with their elements and view partials.
Define Elements
Define your blog elements in config/alchemy/elements.yml.
Post Header
The post header captures all metadata in a single element — headline, author, hero image, and a teaser excerpt:
# config/alchemy/elements.yml
- name: post_header
unique: true
taggable: true
ingredients:
- role: headline
type: Headline
settings:
levels: [1]
- role: author
type: Text
- role: image
type: Picture
settings:
size: 1200x600
crop: true
- role: teaser
type: RichtextTIP
The post's title, URL, and publication date come from the page. The teaser is used as a short excerpt on the overview page. With taggable: true, editors can tag posts with categories.
Post Text
A repeatable text section with a headline. The anchor: from_value setting auto-generates an id attribute from the headline text, enabling deep links to sections within a post:
- name: post_text
ingredients:
- role: headline
type: Headline
settings:
levels: [2, 3]
anchor: from_value
- role: text
type: RichtextPost Picture
A repeatable image element with responsive srcset support:
- name: post_picture
ingredients:
- role: image
type: Picture
settings:
srcset: ["400x", "800x", "1200x"]
sizes: ["(max-width: 600px) 400px", "(max-width: 900px) 800px", "1200px"]Blog Overview
The overview element with an editable headline. Its partial queries and renders the blog posts automatically:
- name: blog_overview
unique: true
ingredients:
- role: headline
type: Headline
settings:
levels: [1]
default: BlogDefine Page Layouts
# config/alchemy/page_layouts.yml
- name: blog
unique: true
cache: false # overview lists child pages, must reflect publish changes
elements:
- blog_overview
autogenerate:
- blog_overview
- name: blogpost
elements:
- post_header
- post_text
- post_picture
autogenerate:
- post_headerTIP
Editors create new blog posts by adding child pages under the blog overview page and selecting the blogpost layout.
Customize Views
Generate the view partials for your elements and page layouts:
bin/rails g alchemy:elements --skip
bin/rails g alchemy:page_layouts --skipThe generated page layout partials render all elements with <%= render_elements %>. For the blog post, wrap it in an <article> tag in app/views/alchemy/page_layouts/_blogpost.html.erb:
<article class="blog-post">
<%= render_elements %>
</article>Then customize the element partials in app/views/alchemy/elements/:
_post_header.html.erb
<%= element_view_for(post_header, tag: "header") do |el| %>
<%= el.render :headline %>
<p class="meta">
By <%= el.render :author %> ·
<time><%= l(page.public_on, format: :long) %></time>
</p>
<%= el.render :teaser %>
<%= el.render :image %>
<% end %>_post_text.html.erb
<%= element_view_for(post_text) do |el| %>
<%= el.render :headline %>
<%= el.render :text %>
<% end %>_post_picture.html.erb
<%= element_view_for(post_picture) do |el| %>
<%= el.render :image %>
<% end %>_blog_overview.html.erb
The overview element lists all published child pages:
<%= element_view_for(blog_overview) do |el| %>
<%= el.render :headline %>
<% posts = blog_overview.page.children.published.order(public_on: :desc) %>
<%= render partial: "post_preview", collection: posts, as: :post %>
<% end %>_post_preview.html.erb
<% cache(post) do %>
<% header = post.elements.find_by(name: "post_header") %>
<% return unless header %>
<article class="post-preview">
<% if header.ingredient_by_role(:image)&.picture %>
<%= render header.ingredient_by_role(:image), size: "600x300", crop: true %>
<% end %>
<h2><%= link_to post.title, show_alchemy_page_path(post) %></h2>
<time><%= l(post.public_on, format: :long) %></time>
<p><%= header.ingredient_by_role(:teaser)&.value&.truncate(200) %></p>
</article>
<% end %>