Building This Blog Part 1
I have always liked playing around with static site generators. They perform well, the end result is package of simple web assets that are easy to deploy, and the developer side of me enjoys version controlling my content and publishing with pull requests. With that in mind, I’m starting off with NimblePublisher. Dashbit has an excellent introduction to building the their own blog with it, and Elixir School has a good write up on the technique as well. That said, Dockyard is always up to something interesting, so I’m keeping my eye on their Beacon CMS project and may switch to that at some point.
Layout
If you use the latest version of Phoenix (1.7.0-rc.2 as of the time of writing), Tailwind is included. The great part about the latest Phoenix is that the front end pipeline uses esbuild, so there are no dependencies on NodeJS and it is super fast. Check out this little guide from Pragmatic Studio on Tailwind in Phoenix.
For a fast start, I opted to use a tailwind UI template. The neat aspect of these templates, is that they are fully baked for a NextJS site. But since I am not building my site in Next, that is less helpful to me. Instead, I used the Flexbox Fast Flow™ method to replicate the major layout components of the template. Yes, I totally made that up, but here is an example of what I am talking about (It is a lot of fun watching Peter Meehan work).
Structure
Router
The initial version of my site consists of two liveview routes (main, and blog)
/lib/personal_web/router.ex
scope "/", PersonalWeb do
pipe_through :browser
live "/", MainLive
live "/blog", BlogLive, :index
live "/blog/:id", BlogLive, :show
end
The blog routes set a @live_acton
(:index
and :show
), which I use to change the layout from an index display to an individual post. I’ll show this in a bit.
Outer Templates
Beginning with the outside and moving inward:
lib/personal_web/components/layouts/
- root.html.heex
- app.html.heex
The root template sets the outer layout and site metadata. This is where I added my favicon overrides, which I write about in detail here.
The app template is where my site layout really begins.
<div class="relative">
<div class="flex flex-row w-full justify-center sm:px-8">
<div class="flex flex-col w-full max-w-7xl lg:px-8">
<.page_header />
<main class="px-8 sm:px-16 lg:px-24">
<div class="mt-9 max-w-7xl">
<%= @inner_content %>
</div>
</main>
</div>
</div>
</div>
This is pretty self explanatory. Every route’s content will be injected into @inner_content
. But here, I have also created a page header component that is present across the whole site. To do that, my page header module has to be included in the module that renders the app layout: lib/personal_web/components/layouts.ex
defmodule PersonalWeb.Layouts do
use PersonalWeb, :html
import PersonalWeb.Components.PageHeader
embed_templates "layouts/*"
end
Alternatively, you can also reference the component’s module directly in the template instead of importing the function.
<div>
<PageHeader.page_header />
</div>
I kinda prefer importing and then all component references look the same.
Components
It’s all components all the way down and components are just functions in modules. I’ve made layout components and blog components for the full post and card for lists.
Index/Show
In the blog live view module, I wanted to switch between the index and individual posts. If you recall, I specified different actions in the router.
lib/personal_web/router.ex
scope "/", PersonalWeb do
pipe_through :browser
live "/", MainLive
live "/blog", BlogLive, :index
live "/blog/:id", BlogLive, :show
end
Then in my blog module, I can easily check the value of the @live_action
variable which is automatically provided in the assigns to render.
lib/personal_web/live/blog_live.ex
def render(assigns) do
~H"""
<%= if @live_action == :show do %>
# ... Individual post
<% else %>
# ... Blog index
<% end %>
"""
end
What’s Next
There will be more posts forthcoming as I expand the functionality of my site.
- Hosting on Fly.io
- CI/CD with Gitlab
- Dark mode toggle
And more! Stay tuned.