The Quickest Way to Build a Drag-and-Drop Landing Page Builder in Rails 8
Often when I am building a SaaS or a client project, and the client asks for a feature I always dread: "Can I have a page where I can build my own landing pages?"
If you try to build a drag-and-drop page builder from scratch using pure Ruby and Javascript, it will take you months. You have to handle layout grids, CSS saving, image uploads, and responsive design.
Instead of reinventing the wheel, the quickest and easiest way to do this in Rails 8 is to embed an open-source library called GrapesJS. It is a massive, production-ready page builder that outputs clean HTML and CSS. We can easily wrap it inside a Stimulus controller and save the output to our Rails database.
Here is how to add a drag-and-drop landing page builder to your Rails app in 5 steps.
STEP 1: The Database Setup
First off, we need a place to save the pages our users create. We don't need a complex schema. A landing page basically just consists of two things: the HTML structure and the CSS styles.
Let's generate a simple Page model:
rails g model Page title:string html_content:text css_content:text
rails db:migrate
STEP 2: Loading GrapesJS
GrapesJS is a heavy library. It requires both Javascript and CSS to work.
For the Javascript, we can use Rails 8 Importmaps to pin it:
bin/importmap pin grapesjs
For the CSS, GrapesJS needs its core stylesheet to make the editor look good. The easiest way to include this without fighting the asset pipeline is to just drop the CDN link directly into the view where the editor will live.
STEP 3: The Editor View
Now we create the view where the user will actually build the page. We will create a div for the editor, and attach a Stimulus controller to it. We also need a "Save" button to send the data back to Rails.
<!-- app/views/pages/edit.html.erb -->
<!-- 1. Load the GrapesJS CSS -->
<link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css">
<div data-controller="page-builder" data-page-builder-id-value="<%= @page.id %>">
<div class="header">
<h2>Editing: <%= @page.title %></h2>
<!-- This button triggers the save method in our Stimulus controller -->
<button data-action="click->page-builder#save">Save Page</button>
</div>
<!-- This is the empty canvas where GrapesJS will inject the builder -->
<div id="gjs" data-page-builder-target="editor">
<!-- Load existing content if the user is editing a saved page -->
<style><%= @page.css_content %></style>
<%= @page.html_content&.html_safe %>
</div>
</div>
STEP 4: The Stimulus Controller (The Magic)
Next, we generate our Stimulus controller to initialize the builder:
rails g stimulus page_builder
Open the file and add the initialization logic. We import GrapesJS, tell it to attach to our #gjs div, and write a simple fetch request to save the data.
// app/javascript/controllers/page_builder_controller.js
import { Controller } from "@hotwired/stimulus"
import grapesjs from "grapesjs"
export default class extends Controller {
static targets = ["editor"]
static values = { id: Number }
connect() {
// Initialize the drag-and-drop builder
this.editor = grapesjs.init({
container: this.editorTarget,
fromElement: true, // Loads the existing HTML/CSS we put in the view
height: '800px',
width: '100%',
storageManager: false, // We will handle saving to our database manually
// Enable the default built-in blocks (text, images, columns, etc)
blockManager: {
appendTo: '#blocks',
}
});
}
save() {
// Grab the clean HTML and CSS generated by the user
const html = this.editor.getHtml();
const css = this.editor.getCss();
const csrfToken = document.querySelector("[name='csrf-token']").content;
// Send it to our Rails controller
fetch(`/pages/${this.idValue}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken
},
body: JSON.stringify({
page: {
html_content: html,
css_content: css
}
})
}).then(response => {
if (response.ok) {
alert("Page saved successfully!");
}
});
}
}
STEP 5: Rendering the Final Page
Now the user has dragged their blocks, changed the colors, and clicked "Save". The data is in our database.
When a public visitor goes to view the page, we just need a very simple Rails controller and a view to spit that code back out.
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
# ... standard update action ...
def show
@page = Page.find(params[:id])
# Tell Rails to use an empty layout so our standard app navbar
# doesn't ruin their custom landing page design
render layout: false
end
end
And the view is literally just two lines of code:
<!-- app/views/pages/show.html.erb -->
<!-- Inject the custom CSS the user created -->
<style>
<%= @page.css_content.html_safe %>
</style>
<!-- Inject the custom HTML -->
<%= @page.html_content.html_safe %>
Summary
Building a drag-and-drop builder sounds like a terrifying task, but by combining GrapesJS with a simple Stimulus controller, you can give your users a massive, professional landing page builder in about 10 minutes.
You don't need React, you don't need Webpack, and you keep all the data safely stored in your own Postgres or SQLite database.
Top comments (0)