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.