How to render markdown in your Rails app

September 22, 2025

If you are like me and you’re regularly spinning up new websites for your side projects, you probably created a handful of legal pages. To save time, I started to build my legal pages with markdown and just render a Notion-style white page with the required text. This allows me to copy and paste my legal pages between my projects. In this blog post, I want to show you how you can set up your rails app to render markdown views. Even if you are using Inertia!

Prerequisites/ Assumptions

  • Rails 8 or newer
  • Tailwind CSS
  • Tailwindcss-Typography to style the text (https://github.com/tailwindlabs/tailwindcss-typography)

Install the Redcarpet gem

Install the Redcarpet gem to render markdown by adding the following to your gemfile

gem "redcarpet"

and run bundle install.

Create an initializer

Create a new file called markdown.db under config/initializers/markdown.rb and add the following content to it.

# frozen_string_literal: true
MARKDOWN = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)

Run rails g controller Legal imprint

Create a folder with your markdown files.

Under views create a new folder called markdown For all your markdown files. Then create your first markdown file called imprint.md

views/markdown/imprint.md

# Imprint

---

**Jon Doe**
Demo Street 10
12345 Demo City
Germany

Update your legal_controller.rb to read you imprint.md and return its content as an instance variable

class LegalController < ApplicationController
  def imprint
    md_path = Rails.root.join("app", "views", "markdown", "imprint.md")
    @content = MARKDOWN.render(File.read(md_path)).html_safe
  end
end

Update your imprint view to render your markdown.

Replace the content of imprint.html.erb with the following code

<div class="p-4">
  <= link_to root_path, class: "inline-flex items-center justify-center w-12 h-12 rounded-full border border-gray-300 bg-white shadow hover:bg-gray-100" do %>
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
      <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/>
    </svg>
  <% end %>
</div>

<div class="p-4 prose mx-auto">
  <= @content %>
</div>

The important part here is if to keep the prose class from Tailwind As your text will have no styling otherwise.

Extra Config for Inertia users

If you are not using Inertia, you can skip this!

If you are using Inertia You probably noticed that your imprint page does not look quite right. We need to make two more modifications.

  1. Create a non-inertia layout.

The following is just a copy & paste of the default Inertia layout minus the react vite tags:

views/layouts/none_inertia.html.erb

<!DOCTYPE html>
<html>
<head>
  <title inertia><= content_for(:title) || "React Starter Kit" %></title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="mobile-web-app-capable" content="yes">
  <= capybara_lockstep if defined?(Capybara::Lockstep) %>
  <= csrf_meta_tags %>
  <= csp_meta_tag %>

  <= yield :head %>

  <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
  <%= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>

  <link rel="icon" href="/icon.png" type="image/png">
  <link rel="icon" href="/icon.svg" type="image/svg+xml">
  <link rel="apple-touch-icon" href="/icon.png">

  <script>
    <%# Enable dark mode based on localStorage or system preference. Inline to avoid FOUC. %>
    document.documentElement.classList.toggle(
      "dark",
      localStorage.appearance === "dark" ||
      (!("appearance" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches),
    );
  </script>

  <%= vite_stylesheet_tag "application" %>
  <%= vite_client_tag %>
</head>

<body>
<= yield %>
</body>
</html>

And then tell your legal_controller.rb to use the new layout by adding layout "none_inertia" to. So the final controller will look like:

# frozen_string_literal: true
class LegalController < ApplicationController
  layout "none_inertia"
  skip_before_action :authenticate
  def imprint
    md_path = Rails.root.join("app", "views", "markdown", "imprint.md")
    @content = MARKDOWN.render(File.read(md_path)).html_safe
  end
end

Then update your application.css and tell Tailwind where it can find your ERB templates.

@source "../../views/**/*.{erb,html,html.erb}";