Under the Hood 2.0: Rails, React
In March of 2020 I decided it was time to rebuild my personal website using some new things I had learned.
I have had a personal website since I finally got a real footing in website building with Jekyll and Foundation, using Markdown pushed to my GitHub repo for posts. My work is more Product than Code, but there's a fair amount in the realm of the actual coding, building things that I can do, and it brings me a lot of joy and makes me more effective in my professional and activist life. And it's fun. I love learning and I love the idea that I can conceive of a thing and then build it and not be held back by all the little parts of the "full stack" that I can't do up to my own standards of excellence.
Since that site was built, I did a lot more work with various large-scale server-side applications (like Rails), and learned a fair amount working in React, mostly on small projects. So I kind of came at this project sideways – my primary goal was actually to learn, to use and build on new knowledge, and to have fun.
So I the final product/goal was decided:
- A personal blog, basically
- With the ability to add some custom pages, which I would be fine to / happy to code myself as needed
- A really nice developer experience and the ability to hook into and play around with little interface things that suit my personal interests like content editing UX, accessibility and language and content that's dynamic and responsive to the audience, etc.
And the initial direction for implementation paths was basically already set based on my recent experience and motivations for the project: A Rails server with a React client. We'd use Rails to define API routes that would return JSON, and the react app would have components that fetch the data and display it to the reader, e.g. <Post id={id} />
Architecting a Solution: Two Apps or One?
How to mount a React app and a Rails app together turned into the biggest question for how to implement this site. I'm not doing much non-standard with the server, and for the front-end I would be happy to use Bootstrap SASS and the Rails asset pipeline.
I knew I didn't want to mess with versioning a server and client app in tandem (maintaining this site is not a job for me, it is an incredibly part-time side-project). But how to mount a React app inside a Rails app is actually a big question all across the community with a few different rapidly-evolving solutions. Here was my list of implementation constraints:
- One repo/project for both Client and Server
- Hostable on Heroku (the only place I really felt good running a Rails server/DB)
- I didn't want a really thick layer of coupling between the Rails and React code. I wanted to feel like I was just writing a Rails server and a React client, keeping things fairly vanilla so that the new skills I learned would be portable
- Both Rails world and React world are fast-changing, with libraries and paradigms and pipelines and dev tools coming in and going out of fashion all the time. And since this is such a part-time thing for me, I had yet another reason to keep things very vanilla, to use very stable dependencies and tools
Two Common Solutions: react-rails and react_on_rails
Much of the debate I was privy to at the time was about these two methods, using either react-rails or react_on_rails, which are Rails gems that allow you to write React components directly inside your Rails views, like this:
<%= react_component(
"PostsPage",
props: @posts_list,
prerender: true
) %>
The approach is exciting for a big production app, especially if you have an existing Rails app and you are trying to migrate over parts of the front end from some other solution to React. At work we had a big giant Rails app with jQuery/AJAX to handle forms and transitions, that badly needed an upgrade to a more React-like solution, so I could see the value of this Component-in-ERB approach.
But for me... I just wanted to learn more about working with Rails and React; I didn't want to learn this middle-layer's special ticks and idiosyncrasies. So I ruled this entire category out and kept looking, not so much for some gem or npm package, but for a really solid example or instructions manual on how to seamlessly place a React app inside a Rails app's natural Javascript asset-building/-serving process.
As my search progressed and the ideal approach started to take shape, I found an absolutely fantastic tutorial on Digital Ocean's community tutorials site that met all my requirements and then some. It's an example site and repo with step by step instructions for how to get started and how each critical piece is added in order. I highly, highly recommend it.
This approach works by placing a pretty standard React app with its own react-router inside the folder /app/javascript/packs
. The Rails app's router has a namespace for /api/v1
and very simple controllers to create, edit, destroy, and update posts. Every other url gets sent off to application.erb, which includes a simple tag provided by webpacker, which is our entry into the React app:
<%= javascript_pack_tag 'Index' %>
At this point, the react-router picks up the work of deciding which components to display, and those components are responsible for knowing how to fetch data from the Rails API. It's nice and clean, a proper Rails app and a proper React app, without much time spent dealing with the layer of glue between them.
Though the glue layer is thin, using the the Rails view as the entry-point to the React bundle means we can use Rails's built-in CSRF tag seamlessly and pick it up in the React app:
# in application.html.erb
<%= csrf_meta_tags %>
// in any React component with a form
token = document.querySelector('meta[name="csrf-token"]').content
This was such a nice touch for me! I really feel like, given the requirements and constraints I had at the time, this solution hit the nail on the head. I'm quite pleased with Digital Ocean for cultivating such a great set of tutorials, and to Chuks Opia for writing this one.
The Final Product
In the end, the final product for the site was kinda-sorta satisfying. I give it a 6, mostly because I just never went very far beyond what was in the tutorial because ultimately, I still felt pretty much out on a limb. I did build a nice little side-by-side realtime-preview editing interface though, which I am quite happy with!
And the design of the site itself, I'm pretty happy there too! It's quite minimalist, but has some of its own flare. It's a bit more "vanilla bootstrap" than I love, but with my own colors and fonts and enough personality that I'm not worried it looks thoughtless.
What I Would Change
There are still some issues from the basic feature set that I just never really got around to dealing with:
- A proper login system to administer content securely without having to go into the database itself
- Some integration with s3 to upload and store images without having to host them on imgur or similar
- A "draft" post status
- Moving other aspects of site management – like the metadata, share links, title, blurb – into the database so they can be updated without changing code
And frankly, if I could do it all over again, I would do a few things differently, either because I've learned more and have different skills constraints, or because some of the choices weren't right in the first place.
I would ditch Bootstrap for TailWind.
What I liked most about Bootstrap 4 over version 3 was that it had so many new utility classes that you could style most things using utilities alone, rather than using (and constantly fighting against) their built-in component concepts. Well, I've since learned that there are others doing this better, truly utility-first front-end frameworks like Tailwind CSS. I've used Tailwind for another project, and I'm pretty much in love. It's just so much easier to use; I write less code, I have fewer abstractions and middle-steps to think about, and the resulting bundle size is a lot smaller.
My work with Bootstrap required me to essentially learn this other language – Bootstrap's own markup+class combinations – and I would over-rely on SASS to let me create a bunch of my own custom classes and components, which meant I was kind of creating my own design language on the fly, which I then had to learn, remember, debug and QA. (In some cases it will of course be worth the time and care to create your own design language, but then again, Tailwind and PostCSS have tools for that too.) If I were doing this over again, I would try Tailwind for my styles.
More Framework than just React
In this case I specifically avoided going down a Server-Side-Rendering rabbit-hole because of the complexity involved, but since the time I initially build this site there have been huge leaps forward in frameworks like Gatsby and NextJS that allow you to "just write react" and also get advanced features like server side rendering, preloading links, lazy loading images, tools to help with your lighthouse scores, and so on. The tools are there; I should catch up; it's not so hard.
I would be keen to try NextJS in the future, and I do plan to try out their blog tutorial to see how I like it. It looks like an interesting mix between "alternative to create-react-app" and "alternative to Gatsby", with a lot of these ecosystem details like Webpack and linting all set up and taken care of, plus some conveniences like their own file-system-based routing system, and a Hook called useSWR that wraps the fetch process in some nice defaults and utilities handy for this "SSR + live-and-reactive" world I want to live in.
And I'm interested in Svelte – it almost seems too good to be true, so why not give it a whirl.
The point is I feel like I should be able to use the site as a bit of a playground for different front-end approaches, while keeping the Rails app, the database, and even the branding/styling pretty consistent and stable. This fits with my overall philosophy toward developing stable but innovative software in small teams and medium-sized networks.
For the networks of activist groups I've spent most of my career working with, their data is the key to their membership and their donor base – so it has to stay stable, and any security breach could tank the organization overnight. But when it comes to providing rich and creative experiences for users, engaging with online content, consuming news and information, sharing on social media and messaging apps, engaging with and creating and remixing and submitting video, photos and audio... we have to be able to be creative with the sites we build! If we don't, we'll lose out to all the other things fighting for people's attention, or to activism businesses like Change dot org and corporate marketing campaigns. But if are able to keep our apps fresh and dynamic, and keep our dev teams limber, so to speak, we can keep innovating and be responsive to great ideas, even when our scale is big and our teams are small.