Part 2: Caching Strategy for Vue + Rails API with Nginx

In Part 1, we explored the request flow between Nginx, Vue (frontend), and Rails (API backend). We also covered how Nginx routes traffic and why caching matters in such a setup.

Now in Part 2, we’ll go deeper into asset caching strategies โ€” specifically tailored for a Rails API-only backend + Vue frontend deployed with Nginx.

๐Ÿ”‘ The Core Idea

  • HTML files (like vite.html) should never be cached. They are the entry point of the SPA and change frequently.
  • Hashed assets (like /vite/index-G34XebCm.js) can be cached for 1 year safely, because the hash ensures cache-busting.
  • Non-hashed assets (images, fonts, legacy JS/CSS) should get short-term caching (e.g., 1 hour).

This split ensures fast repeat visits while avoiding stale deploys.

๐Ÿ“‚ Example: Files in public/vite/

Your build pipeline (via Vite) outputs hashed assets like:

vite/
  index-G34XebCm.js
  DuckType-CommonsRegular-CSozX1Vl.otf
  Allergens-D48ns5vN.css
  LoginModal-DR9oLFAS.js

Notice the random-looking suffixes (G34XebCm, D48ns5vN) โ€” these are hashes. They change whenever the file content changes.

โžก๏ธ That’s why they’re safe to cache for 1 year: a new deploy creates new filenames, so the browser will fetch fresh assets.

By contrast, files like:

assets/
  15_minutes.png
  Sky_background.png

do not have hashes. If you update them, the filename doesn’t change, so the browser might keep showing stale content if cached too long. These need shorter cache lifetimes.


๐Ÿ› ๏ธ Final Nginx Caching Configuration

Here’s the Nginx cache snippet tuned for your setup:

# =====================
# HTML (always no-cache)
# =====================
location = /vite.html {
    add_header Cache-Control "no-cache";
}

location ~* \.html$ {
    add_header Cache-Control "no-cache";
}

# ==============================
# Hashed Vue/Vite assets (1 year)
# ==============================
location ^~ /vite/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

# ==================================================
# Other static assets (non-hashed) - 1 hour caching
# ==================================================
location ~* \.(?:js|css|woff2?|ttf|otf|eot|jpg|jpeg|png|gif|svg|ico)$ {
    add_header Cache-Control "public, max-age=3600";
}

๐Ÿ” Explanation

  • location = /vite.html โ†’ explicitly disables caching for the SPA entry file.
  • location ~* \.html$ โ†’ covers other .html files just in case.
  • location ^~ /vite/ โ†’ everything inside /vite/ (all hashed JS/CSS/images/fonts) gets 1 year caching.
  • Final block โ†’ fallback for other static assets like /assets/*.png, with only 1-hour cache.

โš ๏ธ What Happens If We Misconfigure?

  • If you cache .html โ†’ new deploys wonโ€™t show up, users may stay stuck on the old app shell.
  • If you cache non-hashed images for 1 year โ†’ product images may stay stale even after updates.
  • If you donโ€™t use immutable on hashed assets โ†’ browsers may still revalidate unnecessarily.

๐Ÿ—๏ธ Real-World Examples

  • GitLab uses a similar strategy with hashed Webpack assets, caching them long-term via Nginx and Cloudflare.
  • Discourse does long-term caching of fingerprinted JS/CSS, but keeps HTML dynamic with no-cache.
  • Basecamp (Rails + Hotwire) fingerprints all assets, leveraging 1-year immutable caching.

These projects rely heavily on content hashing + Nginx headers โ€” exactly what we’re setting up here.

โœ… Best Practices Recap

  1. Always fingerprint (hash) assets in production builds.
  2. Cache HTML for 0 seconds, JS/CSS hashed files for 1 year.
  3. Use immutable for hashed assets.
  4. Keep non-hashed assets on short lifetimes or rename them when updated.

This ensures smooth deploys, lightning-fast repeat visits, and no stale content issues.

๐Ÿ“Œ In Part 3, we’ll go deeper into Rails + Passenger integration, showing how Rails API responses fit into this caching strategy (and what not to cache at the API layer).


Useful Rubyย ๐Ÿ’Ž Methods: A Short Guide – Scan, Inject With Performance Analysis

#scan Method

Finds all occurrences of a pattern in a string and returns them as an array.

The scan method in Ruby is a powerful string method that allows you to find all occurrences of a pattern in a string. It returns an array of matches, making it extremely useful for text processing and data extraction tasks.

Basic Syntax

string.scan(pattern) โ†’ array
string.scan(pattern) { |match| block } โ†’ string

Examples

Simple Word matching:

text = "hello world hello ruby"
matches = text.scan(/hello/)
puts matches.inspect
# Output: ["hello", "hello"]

Matching Multiple Patterns

text = "The quick brown fox jumps over the lazy dog"
matches = text.scan(/\b\w{3}\b/)  # Find all 3-letter words
puts matches.inspect
# Output: ["The", "fox", "the", "dog"]

1. Find All Matches

"hello world".scan(/\w+/) # => ["hello", "world"]  

2. Extract Numbers

"Age: 25, Price: $50".scan(/\d+/) # => ["25", "50"]  

3. Matching All Characters

"hello".scan(/./) { |c| puts c }
# Output:
# h
# e
# l
# l
# o

3. Capture Groups (Returns Arrays)

"Name: Alice, Age: 30".scan(/(\w+): (\w+)/)  
# => [["Name", "Alice"], ["Age", "30"]]  

When you use parentheses in your regex, scan returns arrays of captures:

text = "John: 30, Jane: 25, Alex: 40"
matches = text.scan(/(\w+): (\d+)/)
puts matches.inspect
# Output: [["John", "30"], ["Jane", "25"], ["Alex", "40"]]

4. Iterate with a Block

"a1 b2 c3".scan(/(\w)(\d)/) { |letter, num| puts "#{letter} -> #{num}" }  
# Output:  
# a -> 1  
# b -> 2  
# c -> 3  
text = "Prices: $10, $20, $30"
total = 0
text.scan(/\$(\d+)/) { |match| total += match[0].to_i }
puts total
# Output: 60

5. Case-Insensitive Search

"Ruby is COOL!".scan(/cool/i) # => ["COOL"]  

6. Extract Email Addresses

"Email me at test@mail.com".scan(/\S+@\S+/) # => ["test@mail.com"]  
text = "Contact us at support@example.com or sales@company.org"
emails = text.scan(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/)
puts emails.inspect
# Output: ["support@example.com", "sales@company.org"]

Performance Characteristics: Ruby’s #scan Method

The #scan method is generally efficient for most common string processing tasks, but its performance depends on several factors:

  1. String length – Larger strings take longer to process
  2. Pattern complexity – Simple patterns are faster than complex regex
  3. Number of matches – More matches mean more memory allocation

Performance Considerations

1. Time Complexity ๐Ÿงฎ

  • Best case: O(n) where n is string length
  • Worst case: O(n*m) for complex regex patterns (with backtracking)

2. Memory Usage ๐Ÿง 

  • Creates an array with all matches
  • Each match is a new string object (memory intensive for large results)

Benchmark ๐Ÿ“ˆ Examples

require 'benchmark'

large_text = "Lorem ipsum " * 10_000

# Simple word matching
Benchmark.bm do |x|
  x.report("simple scan:") { large_text.scan(/\w+/) }
  x.report("complex scan:") { large_text.scan(/(?:^|\s)(\w+)(?=\s|$)/) }
end

Typical results:

              user     system      total        real
simple scan:  0.020000   0.000000   0.020000 (  0.018123)
complex scan: 0.050000   0.010000   0.060000 (  0.054678)

Optimization Tips ๐Ÿ’ก

  1. Use simpler patterns when possible:
   # Slower
   text.scan(/(?:^|\s)(\w+)(?=\s|$)/)

   # Faster equivalent
   text.scan(/\b\w+\b/)
  1. Avoid capture groups if you don’t need them:
   # Slower (creates match groups)
   text.scan(/(\w+)/)

   # Faster
   text.scan(/\w+/)
  1. Use blocks to avoid large arrays:
   # Stores all matches in memory
   matches = text.scan(pattern)

   # Processes matches without storing
   text.scan(pattern) { |m| process(m) }
  1. Consider alternatives for very large strings:
   # For simple splits, String#split might be faster
   words = text.split

   # For streaming processing, use StringIO

When to Be Cautious โš ๏ธ

  • Processing multi-megabyte strings
  • Using highly complex regular expressions
  • When you only need the first few matches (consider #match instead)

The #scan method is optimized for most common cases, but for performance-critical applications with large inputs, consider benchmarking alternatives.


#injectย Method (aka #reduce)

Enumerable#injectย takes two arguments: a base case and a block.

Each item of theย Enumerableย is passed to the block, and the result of the block is fed into the block again and iterate next item.

In a way the inject function injects the function between the elements of the enumerable. inject is aliased as reduce. You use it when you want to reduce a collection to a single value.

For example:

    product = [ 2, 3, 4 ].inject(1) do |result, next_value|
      result * next_value
    end
    product #=> 24

Purpose

  • Accumulates values by applying an operation to each element in a collection
  • Can produce a single aggregated result or a compound value

Basic Syntax

collection.inject(initial) { |memo, element| block } โ†’ object
collection.inject { |memo, element| block } โ†’ object
collection.inject(symbol) โ†’ object
collection.inject(initial, symbol) โ†’ object

Key Features

  1. Takes an optional initial value
  2. The block receives the memo (accumulator) and current element
  3. Returns the final value of the memo

๐Ÿ‘‰ Examples

1. Summing Numbers

[1, 2, 3].inject(0) { |sum, n| sum + n } # => 6

2. Finding Maximum Value

[4, 2, 7, 1].inject { |max, n| max > n ? max : n } # => 7

3. Building a Hash

[:a, :b, :c].inject({}) { |h, k| h[k] = k.to_s; h }
# => {:a=>"a", :b=>""b"", :c=>"c"}

4. Symbol Shorthand (Ruby 1.9+)

[1, 2, 3].inject(:+) # => 6 (same as sum)
[1, 2, 3].inject(2, :*) # => 12 (2 * 1 * 2 * 3)

5. String Concatenation

%w[r u b y].inject("") { |s, c| s + c.upcase } # => "RUBY"

6. Counting Occurrences

words = %w[apple banana apple cherry apple]
words.inject(Hash.new(0)) { |h, w| h[w] += 1; h }
# => {"apple"=>3, "banana"=>1, "cherry"=>1}

Performance Characteristics: Ruby’s #inject Method

  1. Time Complexity: O(n) – processes each element exactly once
  2. Memory Usage:
  • Generally creates only one accumulator object
  • Avoids intermediate arrays (unlike chained map + reduce)

Benchmark ๐Ÿ“ˆ Examples

require 'benchmark'

large_array = (1..1_000_000).to_a

Benchmark.bm do |x|
  x.report("inject:") { large_array.inject(0, :+) }
  x.report("each + var:") do
    sum = 0
    large_array.each { |n| sum += n }
    sum
  end
end

Typical results show inject is slightly slower than explicit iteration but more concise:

              user     system      total        real
inject:      0.040000   0.000000   0.040000 (  0.042317)
each + var:  0.030000   0.000000   0.030000 (  0.037894)

Optimization Tips ๐Ÿ’ก

  1. Use symbol shorthand when possible (faster than blocks):
   # Faster
   array.inject(:+)

   # Slower
   array.inject { |sum, n| sum + n }
  1. Preallocate mutable objects when building structures:
   # Good for hashes
   items.inject({}) { |h, (k,v)| h[k] = v; h }

   # Better for arrays
   items.inject([]) { |a, e| a << e.transform; a }
  1. Avoid unnecessary object creation in blocks:
   # Bad - creates new string each time
   strings.inject("") { |s, x| s + x.upcase }

   # Good - mutates original string
   strings.inject("") { |s, x| s << x.upcase }
  1. Consider alternatives for simple cases:
   # For simple sums
   array.sum # (Ruby 2.4+) is faster than inject(:+)

   # For concatenation
   array.join is faster than inject(:+)

When to Be Cautious โš ๏ธ

  • With extremely large collections where memory matters
  • When the block operations are very simple (explicit loop may be faster)
  • When building complex nested structures (consider each_with_object)

The inject method provides excellent readability with generally good performance for most use cases.


Profiling ๐Ÿ“Š Ruby on Rails 8 Applications: Essential Tools and Techniques

Introduction

Performance optimization is critical for delivering fast, responsive Rails applications. This comprehensive guide covers the most important profiling tools you should implement in your Rails 8 application, complete with setup instructions and practical examples.

Why Profiling Matters

Before diving into tools, let’s understand why profiling is essential:

  1. Identify bottlenecks: Pinpoint exactly which parts of your application are slowing things down
  2. Optimize resource usage: Reduce memory consumption and CPU usage
  3. Improve user experience: Faster response times lead to happier users
  4. Reduce infrastructure costs: Efficient applications require fewer server resources

Essential Profiling Tools for Rails 8

1. Rack MiniProfiler

What it does: Provides real-time profiling of your application’s performance directly in your browser.

Why it’s important: It’s the quickest way to see performance metrics without leaving your development environment.

Installation:

# Gemfile
gem 'rack-mini-profiler', group: :development

Usage example:
After installation, it automatically appears in your browser’s corner showing:

  • SQL query times
  • Ruby execution time
  • Memory allocation
  • Flamegraphs (with additional setup)

Advantages:

  • No configuration needed for basic setup
  • Shows N+1 query warnings
  • Integrates with Rails out of the box

GitHubhttps://github.com/MiniProfiler/rack-mini-profiler
Documentationhttps://miniprofiler.com/

2. Bullet

What it does: Detects N+1 queries, unused eager loading, and missing counter caches.

Why it’s important: N+1 queries are among the most common performance issues in Rails applications.

Installation:

# Gemfile
gem 'bullet', group: :development

Configuration:

# config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end

Example output:

GET /posts
USE eager loading detected
  Post => [:comments]
  Add to your query: Post.includes([:comments])

Advantages:

  • Catches common ORM performance issues early
  • Provides specific recommendations for fixes
  • Works across all environments

GitHubhttps://github.com/flyerhzm/bullet
Documentationhttps://github.com/flyerhzm/bullet/blob/master/README.md

3. Ruby Prof (and StackProf)

What it does: Low-level Ruby code profiler that shows exactly where time is being spent.

Why it’s important: When you need deep insight into method-level performance characteristics.

Installation:

# Gemfile
gem 'ruby-prof', group: :development
gem 'stackprof', group: :development

Usage example:

# In your controller or service object
result = RubyProf.profile do
  # Code you want to profile
end

printer = RubyProf::GraphPrinter.new(result)
printer.print(STDOUT, {})

For StackProf:

StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump') do
  # Code to profile
end

Advantages:

  • Method-level granularity
  • Multiple output formats (call graphs, flamegraphs)
  • StackProf is sampling-based so has lower overhead

GitHubhttps://github.com/ruby-prof/ruby-prof
Documentationhttps://github.com/ruby-prof/ruby-prof/blob/master/README.md

StackProf Alternative:
GitHubhttps://github.com/tmm1/stackprof
Documentationhttps://github.com/tmm1/stackprof/blob/master/README.md

4. Memory Profiler

What it does: Tracks memory allocations and helps identify memory bloat.

Why it’s important: Memory issues can lead to slow performance and even crashes.

Installation:

# Gemfile
gem 'memory_profiler', group: :development

Usage example:

report = MemoryProfiler.report do
  # Code to profile
end

report.pretty_print(to_file: 'memory_report.txt')

Advantages:

  • Shows allocated objects by class and location
  • Tracks retained memory after GC
  • Helps find memory leaks

GitHubhttps://github.com/SamSaffron/memory_profiler
Documentationhttps://github.com/SamSaffron/memory_profiler/blob/master/README.md

5. Skylight

What it does: Production-grade application performance monitoring (APM).

Why it’s important: Understanding real-world performance characteristics is different from development profiling.

Installation:

# Gemfile
gem 'skylight'

Configuration:

# config/skylight.yml
production:
  authentication: [YOUR_AUTH_TOKEN]

Advantages:

  • Low-overhead production profiling
  • Endpoint-level performance breakdowns
  • Database query analysis
  • Exception tracking

Websitehttps://www.skylight.io
Documentationhttps://docs.skylight.io
GitHubhttps://github.com/skylightio/skylight-ruby

6. AppSignal

What it does: Full-stack performance monitoring and error tracking.

Why it’s important: Provides comprehensive insights across your entire application stack.

Installation:

# Gemfile
gem 'appsignal'

Then run:

bundle exec appsignal install YOUR_PUSH_API_KEY

Advantages:

  • Error tracking alongside performance
  • Host metrics integration
  • Background job monitoring
  • Magic Dashboard for quick insights

Websitehttps://appsignal.com
Documentationhttps://docs.appsignal.com/ruby
GitHubhttps://github.com/appsignal/appsignal-ruby

7. Derailed Benchmarks

What it does: Suite of benchmarks and performance tests for your application.

Why it’s important: Helps catch performance regressions before they hit production.

Installation:

# Gemfile
group :development, :test do
  gem 'derailed_benchmarks'
end

Usage examples:

# Memory usage at boot
bundle exec derailed bundle:mem

# Performance per route
bundle exec derailed exec perf:routes

Advantages:

  • CI-friendly performance testing
  • Memory usage analysis
  • Route-based performance testing

GitHubhttps://github.com/schneems/derailed_benchmarks
Documentationhttps://github.com/schneems/derailed_benchmarks/blob/master/README.md

8. Flamegraph Generation

What it does: Visual representation of where time is being spent in your application.

Why it’s important: Provides an intuitive way to understand call stacks and hot paths.

Installation:

# Gemfile
gem 'flamegraph'
gem 'stackprof' # if not already installed

Usage example:

Flamegraph.generate('flamegraph.html') do
  # Code to profile
end

Advantages:

  • Visual representation of performance
  • Easy to spot hot paths
  • Interactive exploration

GitHubhttps://github.com/SamSaffron/flamegraph
Documentationhttp://samsaffron.github.io/flamegraph/rails-startup.html

Additional Helpful Tools ๐Ÿ”ง

9. Benchmark-ips

Benchmark-ips (iterations per second) is a superior benchmarking tool compared to Ruby’s standard Benchmark library. It provides:

  1. Iterations-per-second measurement – More intuitive than raw time measurements
  2. Statistical analysis – Shows standard deviation between runs
  3. Comparison mode – Easily compare different implementations
  4. Warmup phase – Accounts for JIT and cache warming effects

Benchmark-ips solves these problems and is particularly valuable for:

  • Comparing algorithm implementations
  • Testing performance optimizations
  • Benchmarking gem alternatives
  • Validating performance-critical code

GitHubhttps://github.com/evanphx/benchmark-ips
Documentationhttps://github.com/evanphx/benchmark-ips/blob/master/README.md

Installation
# Gemfile
gem 'benchmark-ips', group: :development
Basic Usage:
require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("addition") { 1 + 2 }
  x.report("addition with to_s") { (1 + 2).to_s }
  x.compare!
end
Advanced Features:
Benchmark.ips do |x|
  x.time = 5 # Run each benchmark for 5 seconds
  x.warmup = 2 # Warmup time of 2 seconds
  
  x.report("Array#each") { [1,2,3].each { |i| i * i } }
  x.report("Array#map") { [1,2,3].map { |i| i * i } }
  
  # Add custom statistics
  x.config(stats: :bootstrap, confidence: 95)
  
  x.compare!
end
# Memory measurement
require 'benchmark/memory'

Benchmark.memory do |x|
  x.report("method1") { ... }
  x.report("method2") { ... }
  x.compare!
end

# Disable GC for more consistent results
Benchmark.ips do |x|
  x.config(time: 5, warmup: 2, suite: GCSuite.new)
end
Sample Output:
Warming up --------------------------------------
            addition    281.899k i/100ms
  addition with to_s    261.831k i/100ms
Calculating -------------------------------------
            addition      8.614M (ยฑ 1.2%) i/s -     43.214M in   5.015800s
  addition with to_s      7.017M (ยฑ 1.8%) i/s -     35.347M in   5.038446s

Comparison:
            addition:  8613594.0 i/s
  addition with to_s:  7016953.3 i/s - 1.23x slower

Key Advantages

  1. Accurate comparisons with statistical significance
  2. Warmup phase eliminates JIT/caching distortions
  3. Memory measurements available through extensions
  4. Customizable reporting with various statistics options

10. Rails Performance (Dashboard)

What is Rails Performance?

Rails Performance is a self-hosted alternative to New Relic/Skylight that provides:

  1. Request performance tracking
  2. Background job monitoring
  3. Slowest endpoints identification
  4. Error tracking
  5. Custom event monitoring
Why It’s Important

For teams that:

  • Can’t use commercial SaaS solutions
  • Need to keep performance data in-house
  • Want historical performance tracking
  • Need simple setup without complex infrastructure

GitHubhttps://github.com/igorkasyanchuk/rails_performance
Documentationhttps://github.com/igorkasyanchuk/rails_performance/blob/master/README.md

Installation
# Gemfile
gem 'rails_performance', group: :development

Then run:

rails g rails_performance:install
rake db:migrate
Configuration
# config/initializers/rails_performance.rb
RailsPerformance.setup do |config|
  config.redis = Redis.new # optional, will use Rails.cache otherwise
  config.duration = 4.hours # store requests for 4 hours
  config.enabled = Rails.env.production?
  config.http_basic_authentication_enabled = true
  config.http_basic_authentication_user_name = 'admin'
  config.http_basic_authentication_password = 'password'
end
Accessing the Dashboard:

After installation, access the dashboard at:

http://localhost:3000/rails/performance

Custom Tracking:

# Track custom events
RailsPerformance.trace("custom_event", tags: { type: "import" }) do
  # Your code here
end

# Track background jobs
class MyJob < ApplicationJob
  around_perform do |job, block|
    RailsPerformance.trace(job.class.name, tags: job.arguments) do
      block.call
    end
  end
end
# Add custom fields to requests
RailsPerformance.attach_extra_payload do |payload|
  payload[:user_id] = current_user.id if current_user
end

# Track slow queries
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  if event.duration > 100 # ms
    RailsPerformance.trace("slow_query", payload: {
      sql: event.payload[:sql],
      duration: event.duration
    })
  end
end
Sample Dashboard Views:
  1. Requests Overview:
    • Average response time
    • Requests per minute
    • Slowest actions
  2. Detailed Request View:
    • SQL queries breakdown
    • View rendering time
    • Memory allocation
  3. Background Jobs:
    • Job execution time
    • Failures
    • Queue times
Key Advantages
  1. Self-hosted solution – No data leaves your infrastructure
  2. Simple setup – No complex dependencies
  3. Historical data – Track performance over time
  4. Custom events – Track any application events
  5. Background jobs – Full visibility into async processes

Implementing a Complete Profiling Strategy

For a comprehensive approach, combine these tools at different stages:

  1. Development:
  • Rack MiniProfiler (always on)
  • Bullet (catch N+1s early)
  • RubyProf/StackProf (for deep dives)
  1. CI Pipeline:
  • Derailed Benchmarks
  • Memory tests
  1. Production:
  • Skylight or AppSignal
  • Error tracking with performance context

Sample Rails 8 Configuration

Here’s how to set up a complete profiling environment in a new Rails 8 app:

# Gemfile

# Development profiling
group :development do
  # Basic profiling
  gem 'rack-mini-profiler'
  gem 'bullet'
  
  # Deep profiling
  gem 'ruby-prof'
  gem 'stackprof'
  gem 'memory_profiler'
  gem 'flamegraph'
  
  # Benchmarking
  gem 'derailed_benchmarks', require: false
  gem 'benchmark-ips'
  
  # Dashboard
  gem 'rails_performance'
end

# Production monitoring (choose one)
group :production do
  gem 'skylight'
  # or
  gem 'appsignal'
  # or
  gem 'newrelic_rpm' # Alternative option
end

Then create an initializer for development profiling:

# config/initializers/profiling.rb
if Rails.env.development?
  require 'rack-mini-profiler'
  Rack::MiniProfilerRails.initialize!(Rails.application)

  Rails.application.config.after_initialize do
    Bullet.enable = true
    Bullet.alert = true
    Bullet.bullet_logger = true
    Bullet.rails_logger = true
  end
end

Conclusion

Profiling your Rails 8 application shouldn’t be an afterthought. By implementing these tools throughout your development lifecycle, you’ll catch performance issues early, maintain a fast application, and provide better user experiences.

Remember:

  • Use development tools like MiniProfiler and Bullet daily
  • Run deeper profiles with RubyProf before optimization work
  • Monitor production with Skylight or AppSignal
  • Establish performance benchmarks with Derailed

With this toolkit, you’ll be well-equipped to build and maintain high-performance Rails 8 applications.

Enjoy Rails! ๐Ÿš€