BLOG POSTS
    MangoHost Blog / How to Set Up a Ruby on Rails v7 Project with React Frontend on Ubuntu 24
How to Set Up a Ruby on Rails v7 Project with React Frontend on Ubuntu 24

How to Set Up a Ruby on Rails v7 Project with React Frontend on Ubuntu 24

Setting up a modern web application with Ruby on Rails 7 and React on Ubuntu 24 is becoming increasingly popular as developers look to combine Rails’ robust backend capabilities with React’s dynamic frontend experience. This guide will walk you through creating a full-stack application that leverages Rails 7’s new features like Importmap and Hotwire, while integrating a React frontend for maximum flexibility. You’ll learn how to configure both frameworks, handle data flow between them, and deploy everything on Ubuntu 24’s latest environment.

How Rails 7 and React Integration Works

Rails 7 introduced significant changes to how frontend assets are handled, moving away from Webpacker to embrace modern JavaScript tooling. When integrating React, you have several architectural choices:

  • Full separation: Rails as pure API backend, React as standalone frontend
  • Hybrid approach: Rails views for some pages, React components for interactive features
  • Single Page Application: Rails serves the initial React app, handles routing via React Router

The most flexible approach involves setting up Rails as an API-first backend while serving a React frontend from the same domain. This maintains the benefits of Rails’ conventions while giving you React’s component ecosystem.

Integration Method Complexity Performance SEO Friendly Best For
Full Separation Medium High Requires SSR Large applications
Hybrid Approach High Medium Yes Gradual migration
SPA with Rails API Low High Requires SSR Modern web apps

Prerequisites and System Setup

Before diving into the setup, ensure your Ubuntu 24 system has the necessary dependencies. Ubuntu 24’s package repositories include updated versions of most tools we’ll need.

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install essential build tools
sudo apt install -y curl wget gnupg2 software-properties-common build-essential

# Install Node.js 20 (recommended for React)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Install Ruby version manager (rbenv)
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc

# Install ruby-build plugin
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Install Ruby 3.2.0 (latest stable version supported by Rails 7):

# Install Ruby dependencies
sudo apt install -y libssl-dev libreadline-dev zlib1g-dev autoconf bison libyaml-dev libreadline-dev libncurses5-dev libffi-dev libgdbm-dev

# Install Ruby 3.2.0
rbenv install 3.2.0
rbenv global 3.2.0

# Verify installation
ruby -v
gem -v

Setting Up Rails 7 Backend

Rails 7 ships with several new defaults that make API development smoother. We’ll create a Rails application configured primarily as an API with some view capabilities for serving our React frontend.

# Install Rails 7
gem install rails -v "~> 7.0"

# Create new Rails project
rails new my_rails_react_app --api --database=postgresql
cd my_rails_react_app

# Add necessary gems to Gemfile
echo "
gem 'rack-cors'
gem 'jbuilder'
gem 'image_processing', '~> 1.2'
" >> Gemfile

bundle install

Configure CORS for React frontend communication:

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3001', '127.0.0.1:3001'  # React dev server
    
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      credentials: true
  end
end

Create a sample API controller to test the setup:

# app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]

  def index
    @posts = Post.all
    render json: @posts
  end

  def show
    render json: @post
  end

  def create
    @post = Post.new(post_params)
    
    if @post.save
      render json: @post, status: :created
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  def update
    if @post.update(post_params)
      render json: @post
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  def destroy
    @post.destroy
    head :no_content
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end

  def post_params
    params.require(:post).permit(:title, :content, :author)
  end
end

Generate the Post model and run migrations:

# Generate model
rails generate model Post title:string content:text author:string

# Setup database (install PostgreSQL if needed)
sudo apt install -y postgresql postgresql-contrib libpq-dev
sudo systemctl start postgresql
sudo systemctl enable postgresql

# Create database user
sudo -u postgres createuser -s $(whoami)
sudo -u postgres createdb $(whoami)

# Run migrations
rails db:create
rails db:migrate

Update routes for API endpoints:

# config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :posts
    end
  end
  
  # Serve React app for all other routes
  get '*path', to: 'application#fallback_index_html', constraints: ->(request) do
    !request.xhr? && request.format.html?
  end
end

React Frontend Setup

We'll create the React frontend in a separate directory within our Rails project to keep things organized while maintaining the ability to serve everything from one domain in production.

# Create React app in frontend directory
npx create-react-app frontend --template typescript
cd frontend

# Install additional dependencies
npm install axios react-router-dom @types/react-router-dom

# Install development dependencies
npm install -D @types/axios concurrently

Configure the React development server to run on port 3001 to avoid conflicts with Rails:

# frontend/package.json
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    // ... existing dependencies
  },
  "scripts": {
    "start": "PORT=3001 react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "proxy": "http://localhost:3000"
}

Create a service for API communication:

// frontend/src/services/api.ts
import axios from 'axios';

const API_BASE_URL = process.env.NODE_ENV === 'production' 
  ? '/api/v1' 
  : 'http://localhost:3000/api/v1';

const api = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  withCredentials: true,
});

export interface Post {
  id: number;
  title: string;
  content: string;
  author: string;
  created_at: string;
  updated_at: string;
}

export const postsAPI = {
  getAll: () => api.get('/posts'),
  getById: (id: number) => api.get(`/posts/${id}`),
  create: (post: Omit) => 
    api.post('/posts', { post }),
  update: (id: number, post: Partial) => 
    api.put(`/posts/${id}`, { post }),
  delete: (id: number) => api.delete(`/posts/${id}`),
};

export default api;

Create a React component that interacts with the Rails API:

// frontend/src/components/PostsList.tsx
import React, { useState, useEffect } from 'react';
import { postsAPI, Post } from '../services/api';

const PostsList: React.FC = () => {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchPosts();
  }, []);

  const fetchPosts = async () => {
    try {
      setLoading(true);
      const response = await postsAPI.getAll();
      setPosts(response.data);
    } catch (err) {
      setError('Failed to fetch posts');
      console.error('Error fetching posts:', err);
    } finally {
      setLoading(false);
    }
  };

  const handleDelete = async (id: number) => {
    try {
      await postsAPI.delete(id);
      setPosts(posts.filter(post => post.id !== id));
    } catch (err) {
      setError('Failed to delete post');
    }
  };

  if (loading) return 
Loading...
; if (error) return
Error: {error}
; return (

Posts

{posts.length === 0 ? (

No posts found.

) : (
    {posts.map(post => (
  • {post.title}

    By: {post.author}

    {post.content}

  • ))}
)}
); }; export default PostsList;

Production Build and Deployment Setup

For production, we need to configure Rails to serve the React build files and handle routing properly.

First, update the Rails application controller to handle the React app fallback:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  def fallback_index_html
    render file: Rails.root.join('public', 'index.html')
  end
end

Create a build script that integrates React build with Rails:

# bin/build_frontend
#!/bin/bash
set -e

echo "Building React frontend..."
cd frontend
npm run build

echo "Copying build files to Rails public directory..."
cd ..
rm -rf public/static
cp -r frontend/build/* public/
cp frontend/build/index.html app/views/layouts/

echo "Frontend build complete!"

Make the script executable:

chmod +x bin/build_frontend

Create a development script to run both servers concurrently:

# package.json (in project root)
{
  "name": "rails-react-app",
  "scripts": {
    "dev": "concurrently \"rails server\" \"cd frontend && npm start\"",
    "build": "./bin/build_frontend",
    "start": "rails server"
  },
  "devDependencies": {
    "concurrently": "^7.6.0"
  }
}

Common Issues and Troubleshooting

Several issues commonly arise when setting up Rails 7 with React. Here are the most frequent problems and their solutions:

  • CORS Issues: If your React app can't communicate with Rails, double-check the CORS configuration and ensure the origins match your development URLs exactly.
  • Asset Serving Problems: Rails 7 changed how assets are handled. Make sure your build script correctly copies React build files to the Rails public directory.
  • Routing Conflicts: The catch-all route for React should be the last route in your Rails routes file to avoid conflicts with API endpoints.
  • Database Connection Issues: Ubuntu 24 might have different PostgreSQL configurations. Check the database.yml file and ensure the database user has proper permissions.

Here's a troubleshooting checklist for common deployment issues:

# Check Rails server status
curl -I http://localhost:3000/api/v1/posts

# Test React build
cd frontend && npm run build
ls -la build/

# Verify database connection
rails db:migrate:status

# Check for JavaScript console errors
# Open browser dev tools when accessing the app

# Test API endpoints directly
curl -X GET http://localhost:3000/api/v1/posts \
  -H "Content-Type: application/json"

Performance Optimization and Best Practices

To ensure optimal performance in production, consider these configuration adjustments:

# config/environments/production.rb
Rails.application.configure do
  # Enable serving of images, stylesheets, and JavaScripts from an asset server
  config.asset_host = ENV['CDN_HOST'] if ENV['CDN_HOST']
  
  # Compress CSS using a preprocessor
  config.assets.css_compressor = :sass
  
  # Enable gzip compression
  config.middleware.use Rack::Deflater
  
  # Cache React build files
  config.public_file_server.headers = {
    'Cache-Control' => 'public, max-age=31536000'
  }
end

For the React frontend, optimize the build process:

# frontend/.env.production
GENERATE_SOURCEMAP=false
INLINE_RUNTIME_CHUNK=false

Performance comparison between different setup approaches:

Metric Rails + React (Integrated) Separate Deployments Rails with Hotwire Only
Initial Load Time ~800ms ~1200ms ~400ms
Subsequent Navigation ~50ms ~100ms ~200ms
Build Complexity Medium High Low
Server Requirements Single server Multiple servers Single server

Real-World Use Cases and Examples

This Rails 7 + React setup works particularly well for:

  • Dashboard Applications: Administrative interfaces where you need rich data visualization alongside traditional CRUD operations
  • E-commerce Platforms: Product catalogs with interactive features while maintaining SEO-friendly server-rendered pages
  • Content Management Systems: Editorial interfaces that benefit from React's component reusability
  • API-First Applications: Services that need to support multiple frontend clients (web, mobile, third-party integrations)

A practical example might be a blog platform where Rails handles user authentication, content management, and SEO-optimized page rendering, while React manages the comment system, real-time notifications, and interactive content editor.

For production deployment on Ubuntu 24, consider using systemd services:

# /etc/systemd/system/rails-react-app.service
[Unit]
Description=Rails React Application
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/rails-react-app
Environment=RAILS_ENV=production
Environment=PORT=3000
ExecStart=/home/deploy/.rbenv/shims/rails server
Restart=always

[Install]
WantedBy=multi-user.target

This setup provides a solid foundation for modern web applications that need the robustness of Rails with the interactivity of React. The key is maintaining clear separation of concerns while leveraging the strengths of both frameworks.

For more detailed information, check the official Rails Getting Started Guide and React Documentation.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked