Why you don't need SPA

By Guillaume Briday

Before starting

  • Quick introduction
  • Not a silver bullet
  • Know your context

Why?

  • More interactive applications
  • More features to ship
  • Reduce the overall complexity
  • To reduce scalability issues?

Back in my days...

Plain AJAX requests

        
$("#my-button").click(function () {
  const name = $("#city-select").val()

  $.ajax({
    url: `/my/awesome/api?name=${name}`,
    done (data) {
      const div = $("
    ") data.forEach((item) => { div.append(`
  • ${item.name}
  • `) }) $("#my-content").html(div) } }) })

    Rails UJS & SJR

                
                  def create
                    @user = User.new(params[:user])
    
                    respond_to do |format|
                      if @user.save
                        format.html { ... }
                        format.js
                      else
                        format.html { ... }
                        format.js
                      end
                    end
                  end
                
              
                
                  // app/views/users/create.js.erb
    
                  const users = document.querySelector("#users");
                  users.insertAdjacentHTML("beforeend", "<%= j render(@user) %>");
                
              

    Rails UJS & SJR

    It was cool but...

    Rails UJS & SJR

    • Hard to maintain in complex UIs
    • Hard to scale in big teams
    • Configuration over convention
    • Lots of stuff to write manually
    • Most Back-end frameworks don't support that

    Here come the big guys

    Front-end Frameworks & SPAs

    • Render happens in the browser
    • Fetch data in JSON via AJAX
    • DOM matches the data

    React component

      
    import { useState } from 'react'
    
    function MyComponent() {
      const [name, setName] = useState('')
      const [items, setItems] = useState([])
    
      useEffect(() => {
        fetch(`/my/awesome/api?name=${name}`)
          .then(res => res.json())
          .then((data) => {
            setItems(data)
          })
      }, [name])
    
      return (
        ...
    
        
      {items.map(item => (
    • {item.name}
    • ))}
    ) }

    From manipulating DOM with jQuery to manipulate Data with React.

    • Much easier... in theory

    How does it work?

    Front-end Frameworks & SPAs

    Front-end Frameworks & SPAs

    Front-end Frameworks & SPAs

    Incredible hidden complexity

    • Business Logic Duplication
    • Overall performance issues
    • Complex toolchain (Bundle splitting, Pre-rendering, Webpack, etc)
    • API and data interchanges
    • Organizational challenges
    • Tons of JS

    Front-end Frameworks & SPAs

    But why?

    • To prevent full page reload?
    • To add interactive actions? (likes, comments, charts, etc)
    • To refresh parts of the page dynamically
    • For scalability issues?
    • To separate concerns?
    • You are not Facebook

    Here comes Hotwire

    Maybe you just need Turbo with Stimulus...

    And https://www.stimulus-components.com/ πŸ˜‡

    Basic concept

    Turbo Frames

    Turbo Frames

                
                  <%# app/views/todos/show.html.erb %>
    
                  

    My awesome todo

    <%= turbo_frame_tag @todo do %>

    <%= @todo.name %>

    <%= @todo.description %>

    <%= link_to 'Edit this todo', edit_todo_path(@todo) %> <% end %>
                
                  <%# app/views/todos/edit.html.erb %>
    
                  

    Editing message

    <%= turbo_frame_tag @todo do %> <%# form_with ... %> <% end %>

    Turbo Frames

                
    class CommentsController < ApplicationController
      def index
        budget = Budget.find(params[:budget_id])
    
        render partial: 'comments/comments', locals: { comments: budget.comments }
      end
    end
                
              
                
    
    <%= turbo_frame_tag :comments, src: comments_path(budget_id: @budget.id), loading: 'lazy' do <%# Adding a nice loader that will be replaced once the partial gets loaded. %> <% end %>

    Turbo Frames

    Turbo Frames

    • Update just what you need
    • No JavaScript required
    • Basically instant
    • Everything stay on the server side
    • Back-end dependent
    • Progressive enhancement is possible
    • Plug'n'play

    What if you need to update multiple frames at once

    Or update frames from HTTP responses?

    Turbo Stream

                
                  <%# app/views/todos/show.html.erb %>
    
                  <%= turbo_stream_from @todo %>
    
                  

    <%= @todo.name %>

    Turbo Stream

                
                  class Todo < ApplicationRecord
                    # broadcasts
                    after_create_commit -> { broadcast_append_to self }
                    after_destroy_commit -> { broadcast_remove_to self }
                    after_update_commit -> { broadcast_replace_to self }
                  end
                
              

    Demo

    You now πŸ‘‡

    Don't reinvent the wheel

    • GraphQL
    • React SSR (SEO, Speed, db calls)
    • Fetch / Link / Routers
    • State managers
    • Form & AJAX libraries
    • Only half of your app

    Don't reinvent the wheel

    Remember: Nobody cares

    • Most website have page reload
    • High interactivity websites are really rare
    • Focus on your Time to Market
    • And on your customers needs
    • Customers don't care. You don't too.
    • Make your app fast and add cache.

    Remember: Nobody cares

    • Backend is boring. Because it's a solved problem.
    • Don't fall into the Hype Driven Development.

    Conclusion

    • SPA are great, just use it wisely.
    • Choose the right tool for your (real) needs.

    Great posts

    me
    @guillaumebriday

    Thanks ! πŸ™