Skip to content

Commit 0fa4499

Browse files
CasJamBrian Caselclaude
authored
Build SSR bundle in production + Hatchbox deployment guide (#6)
* Build Inertia SSR bundle during assets:precompile Flip vite_ruby's ssrBuildEnabled to true so production deploys produce public/vite-ssr/ssr.js. Without this, the Inertia Rails renderer silently falls back to client-only rendering, leaving crawlers and LLM agents with an empty <div id="app"> on public pages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add Hatchbox deployment guide Quick reference covering cluster setup, env vars (RAILS_MASTER_KEY, APP_HOST), database provisioning, domain config, the SSR Node process (bin/vite ssr on the web role), the Solid Queue jobs process (bin/jobs on the worker role), and post-deploy verification steps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Brian Casel <brian@briancasel.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent efb6859 commit 0fa4499

2 files changed

Lines changed: 114 additions & 1 deletion

File tree

config/vite.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"all": {
33
"sourceCodeDir": "app/javascript",
4-
"watchAdditionalPaths": []
4+
"watchAdditionalPaths": [],
5+
"ssrBuildEnabled": true
56
},
67
"development": {
78
"autoBuild": true,

docs/hatchbox-deployment-guide.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Hatchbox deployment guide
2+
3+
Quick reference for deploying an app generated from this template to Hatchbox. Steps are in the order you'd do them in the Hatchbox UI.
4+
5+
## 1. Spin up a cluster (server)
6+
7+
Create a new cluster in Hatchbox — typically on Digital Ocean. The cluster needs these roles:
8+
9+
- **Web** — Rails app + the SSR Node process
10+
- **Worker** — Solid Queue background jobs (`bin/jobs`)
11+
- **Cron** — scheduled jobs (only needed if you add any; safe to include by default)
12+
- **PostgreSQL** — the single database that backs Active Record, Solid Queue, Solid Cache, and Solid Cable
13+
14+
A single-server cluster with all roles is fine for a small app. Split them later if you outgrow it.
15+
16+
Make sure the server image has compatible Ruby and Node versions — this app expects:
17+
18+
- Ruby `3.3.6` (from `.ruby-version`)
19+
- Node `22.12.0` (from `.nvmrc`)
20+
21+
## 2. Create the app
22+
23+
- Hatchbox dashboard → New App
24+
- Connect your GitHub repo and pick the branch to deploy (e.g. `main` or `staging`)
25+
- (Optional) Enable auto-deploy on push for that branch
26+
27+
## 3. Set environment variables
28+
29+
App → **Environment**. At minimum:
30+
31+
| Variable | Value | Notes |
32+
| --- | --- | --- |
33+
| `RAILS_MASTER_KEY` | Contents of `config/master.key` (or `config/credentials/production.key` if you've set up environment-scoped credentials) | Required. Do **not** commit the key file. |
34+
| `APP_HOST` | `https://yourdomain.com` | Used by `config/sitemap.rb` to build absolute URLs in `public/sitemap.xml`. Without it, the sitemap falls back to `https://example.com`. |
35+
36+
Optional / situational:
37+
38+
| Variable | Value | When to set it |
39+
| --- | --- | --- |
40+
| `INERTIA_SSR` | `1` or `0` | SSR is **on by default in production** (see `config/initializers/inertia_rails.rb`). Set `0` to force-disable, e.g. for debugging. |
41+
| `INERTIA_SSR_URL` | `http://localhost:13714` | Only needed if you move the SSR process to a non-default port. |
42+
| `DATABASE_URL` | postgres URL | Hatchbox usually sets this automatically when you attach the database (step 4). Only override if you're pointing at an external database. |
43+
44+
## 4. Create the database
45+
46+
App → **Databases** → click the button to create a PostgreSQL database for this app. Hatchbox provisions it on the PostgreSQL-role server and wires `DATABASE_URL` into the app automatically.
47+
48+
This single database is also used by Solid Queue, Solid Cache, and Solid Cable — no separate databases needed (see `CLAUDE.md`).
49+
50+
## 5. Set up the domain
51+
52+
App → **Domains & SSL**:
53+
54+
- Add your domain (e.g. `yourdomain.com` and/or `www.yourdomain.com`)
55+
- Point DNS at the Hatchbox server IP (A record for apex, CNAME for `www`)
56+
- Let Hatchbox auto-provision Let's Encrypt SSL once DNS propagates
57+
58+
## 6. Create the SSR process
59+
60+
App → **Processes** → Add Process. This runs the long-lived Node server that handles Inertia SSR requests from Rails.
61+
62+
| Field | Value |
63+
| --- | --- |
64+
| Runs on | **web** |
65+
| Process name | `ssr` |
66+
| Start command | `bin/vite ssr` |
67+
| Reload command | *(empty)* |
68+
| Stop command | *(empty)* |
69+
| Restart this process on every deploy | ✅ checked |
70+
| Socket activation | unchecked |
71+
72+
**Why this matters:** the Inertia Rails renderer POSTs page renders to this Node process at `http://localhost:13714`. If the process isn't running, Inertia silently falls back to client-only rendering — crawlers (Google, GPTBot, ClaudeBot, etc.) see an empty `<div id="app">` on public pages.
73+
74+
The SSR bundle itself (`public/vite-ssr/ssr.js`) is built automatically during `assets:precompile` because `config/vite.json` has `"ssrBuildEnabled": true`. No extra build step needed.
75+
76+
## 7. Create the jobs process
77+
78+
App → **Processes** → Add Process. This runs Solid Queue.
79+
80+
| Field | Value |
81+
| --- | --- |
82+
| Runs on | **worker** |
83+
| Process name | `jobs` |
84+
| Start command | `bin/jobs` |
85+
| Restart this process on every deploy | ✅ checked |
86+
87+
## 8. Deploy
88+
89+
App → **Deploy**. Hatchbox will:
90+
91+
1. Pull the latest commit
92+
2. Run `bundle install` and `npm install` (or `bun install`)
93+
3. Run `assets:precompile` — produces both `public/vite/` (client) and `public/vite-ssr/ssr.js` (SSR)
94+
4. Run `db:prepare` (migrations + seeds on first deploy)
95+
5. Boot the web process + restart the `ssr` and `jobs` processes
96+
97+
## Post-deploy checks
98+
99+
- Visit the site and **view source** on a public page — `<div id="app">` should contain rendered HTML, not be empty. If it's empty, SSR isn't reaching the Node process (check the `ssr` process logs in Hatchbox).
100+
- Visit `/sitemap.xml` (after deploy + sitemap regen) — URLs should use your real domain, not `example.com`. If they don't, `APP_HOST` isn't set.
101+
- Update `public/robots.txt` so the `Sitemap:` line points at your real domain (it ships pointing at `https://example.com/sitemap.xml`).
102+
- Trigger a background job to confirm Solid Queue is processing — check the `jobs` process logs.
103+
104+
## Troubleshooting
105+
106+
**Blank page / empty `<div id="app">` in view source.** SSR Node process isn't running or isn't reachable. Check the `ssr` process logs. Confirm `public/vite-ssr/ssr.js` exists in the deployed release (it should, after the `ssrBuildEnabled` fix).
107+
108+
**`ActiveRecord::ConnectionNotEstablished` or queue/cache errors.** The single PostgreSQL DB powers all four (Active Record + Solid Queue/Cache/Cable). Make sure `DATABASE_URL` is set and the DB was provisioned in step 4.
109+
110+
**`Missing secret_key_base` or credentials errors.** `RAILS_MASTER_KEY` not set or doesn't match the encrypted credentials file in the repo.
111+
112+
**Sitemap shows `example.com` URLs.** `APP_HOST` env var not set.

0 commit comments

Comments
 (0)