Skip to content

shakacode/react_on_rails-hacker-news-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React on Rails Hacker News App

This repository is a Hacker News demo built with Rails 8, React 19, and React on Rails Pro React Server Components (RSC).

It recreates the core experience of the Vercel next-react-server-components demo in a Rails-first application:

  • Story feeds for top, new, best, ask, show, and job
  • Item pages with streamed nested comments
  • User profile pages
  • Rails-managed HTTP caching and 404 handling
  • React Server Components rendered through the React on Rails Pro Node renderer

Reference projects:

Live Demo

Quick Start

Prerequisites

  • Ruby 3.4.3
  • Node.js 24.8.0
  • pnpm
  • PostgreSQL
  • Optional: mise to match .tool-versions

Install and Run

If you use mise:

mise install
bin/setup --skip-server
bin/dev

If you manage runtimes yourself:

bundle install
pnpm install
bin/rails db:prepare
bin/dev

Then open http://localhost:3000.

Useful Development Commands

bin/dev           # Rails + client bundle + server bundle + node renderer + RSC bundle
bin/dev static    # Static asset watch mode
bin/dev prod      # Development with production-style assets
pnpm exec tsc --noEmit
bin/rubocop
bin/rails test
bin/rails test:system

Routes

  • / and /news/:page render story feeds
  • /item/:id renders a story detail page or direct comment page
  • /user/:id renders a Hacker News user profile
  • /rsc_payload/:component_name streams the React Server Component payload used by React on Rails Pro

Architecture

This app keeps Rails in charge of routing, controllers, caching, and HTML entrypoints, while React on Rails Pro handles the RSC runtime and the Node renderer.

Request Flow

  1. A Rails route hits StoriesController, ItemsController, or UsersController.
  2. The controller prepares props, preflights missing Hacker News resources, and applies HTTP caching headers.
  3. The .erb view calls stream_react_component(...).
  4. React on Rails Pro serves the initial shell and opens an RSC stream through /rsc_payload/:component_name.
  5. The Node renderer executes rsc-bundle.js, resolves async server components, and streams Suspense boundaries back to the browser.
  6. Only the minimal client code needed for interactivity is hydrated on the page.

Key Rails Pieces

  • app/controllers/ manages route params, 404s, and cache headers
  • app/views/*/show.html.erb and app/views/stories/index.html.erb are the Rails entrypoints for streamed components
  • lib/hacker_news_client.rb is the Ruby-side preflight client used for missing-resource checks

Key React Pieces

  • app/javascript/src/hn/ror_components/ contains top-level route components registered with React on Rails Pro
  • app/javascript/src/hn/components/ contains async server components, presentational components, and minimal interactive surfaces
  • app/javascript/src/hn/lib/ contains Hacker News API access, types, and view-model mappers
  • client/node-renderer.js configures the React on Rails Pro Node renderer

Build and Runtime Notes

  • The demo uses Webpack for asset bundling because the current RSC manifest flow in this app depends on it
  • The Node renderer runs separately from Rails in development on port 3800
  • hnApi.ts uses Node http and https instead of relying on global fetch, which keeps it compatible with the Node renderer VM

File Structure

app/
  controllers/
    stories_controller.rb
    items_controller.rb
    users_controller.rb
  views/
    stories/index.html.erb
    items/show.html.erb
    users/show.html.erb
  javascript/src/hn/
    ror_components/
      HNStoriesPage.tsx
      HNItemPage.tsx
      HNUserPage.tsx
    components/
      Stories.tsx
      ItemPage.tsx
      UserPage.tsx
      Comments.tsx
      Comment.tsx
      CommentToggle.tsx
      Story.client.tsx
    lib/
      hnApi.ts
      mappers.ts
      server.ts
      types.ts
client/
  node-renderer.js
config/
  initializers/react_on_rails_pro.rb
  webpack/
test/
  integration/http_caching_test.rb
  system/hacker_news_app_test.rb
  support/
    fake_hacker_news_api.rb
    node_renderer_test_server.rb

Key RSC Patterns

1. Rails Starts the Stream

Each route is still a normal Rails action plus a normal Rails view:

<%= stream_react_component("HNStoriesPage", props: @hn_stories_props, prerender: true) %>

That keeps routing and response behavior in Rails instead of moving it into a JavaScript router.

2. Async Server Components Fetch Their Own Data

Stories.tsx is an async server component. It fetches story IDs, then streams story rows behind Suspense boundaries:

export default async function Stories({ page, storyType }: StoriesProps) {
  const storyPage = await fetchStoryPage(storyType, page);

  return storyPage.ids.map((id, offset) => (
    <Suspense fallback={<StoryRowFallback rank={offset + 1} />} key={id}>
      <StoryRow id={id} rank={offset + 1} />
    </Suspense>
  ));
}

The same pattern is used for nested comments in Comments.tsx.

3. Server-Side Data Mapping Stays Explicit

app/javascript/src/hn/lib/mappers.ts converts raw Hacker News payloads into stable view models before rendering. That keeps rendering code simple and avoids leaking API edge cases into components.

4. Keep the Client Surface Small

This demo keeps most logic on the server:

  • Story.client.tsx is a tiny client component for the story row surface
  • CommentToggle.tsx uses native <details> and <summary> so comment collapsing works without a hook-based client boundary
  • Data fetching, pagination, comment recursion, and user pages all stay server-rendered

5. Rails HTTP Caching Wraps the RSC Experience

Controllers still own HTTP semantics:

apply_public_cache(ttl: 5.minutes, etag: [ "item", @hn_item_props[:itemId] ])

That means the app can use Rails cache headers, ETags, and 404 responses without giving up streamed RSC rendering.

Comparison with the Next.js Version

Concern Next.js Reference This App
Routing App Router / file-system routes Rails routes + Rails controllers
Entry HTML React/Next page tree Rails view calls stream_react_component
Data fetching Server components inside Next.js runtime Server components inside React on Rails Pro Node renderer
RSC transport Built into Next.js Explicit /rsc_payload/:component_name route
HTTP caching Typically handled in Next.js route handlers or hosting layer Standard Rails controller cache headers and ETags
404 handling Next.js route conventions Ruby preflight checks plus Rails status codes
Deployment shape Next.js app runtime Rails app plus Node renderer process

The important similarity is the rendering model: async server components, Suspense boundaries, progressive streaming, and minimal client-side JavaScript.

The important difference is ownership: Rails remains the application shell, request orchestrator, and caching layer.

Testing

The test suite uses a deterministic fake Hacker News API plus a dedicated Node renderer test server.

  • test/integration/http_caching_test.rb verifies cache headers and 404 behavior
  • test/system/hacker_news_app_test.rb verifies feed rendering, nested comments, comment collapsing, and user pages
  • test/support/fake_hacker_news_api.rb removes the external Hacker News API dependency during tests
  • test/support/node_renderer_test_server.rb boots the renderer against compiled test assets

Learn More

Contributing

Contributions are welcome.

  • Open an issue before large scope changes
  • Keep Rails routing/controller concerns separate from React rendering concerns
  • Prefer server components by default and add client code only when interactivity requires it
  • Run local checks before opening a pull request:
pnpm exec tsc --noEmit
bin/rubocop
bin/rails test
bin/rails test:system

About

Hacker News demo built with Rails 8, React on Rails Pro, and React Server Components

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors