CLI tool
tricknotes/ember-cli-rails avatar
tricknotes/ember-cli-rails

ember-cli-rails: serving an Ember CLI app from inside a Rails process

Unify your EmberCLI and Rails Workflows

707 stars201 forksRubyMIT

At a glance

What is it?
The ember-cli-rails gem mounts an Ember CLI application into a Rails app, builds it for you, and, for Vite based apps, runs the Vite dev server as a child of Rails. It is a small integration layer with sharp version boundaries.
Who is it for?
Adopt ember-cli-rails if you already run Rails and want one process, one domain, and one deploy for an Ember CLI front end, and accept that the classic Broccoli path still depends on a separate addon whose major and minor versions must track the gem. Do not adopt it for a Vite based app expecting the addon, since the README calls that combination incompatible, and do not treat the 2017 release tags as the current state of the project.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One process and one domain instead of two apps and a CORS header

The install path is short. The gem is added to the Gemfile:

ruby
gem "ember-cli-rails"

and the Ruby dependencies are installed with:

bash
$ bundle install

If the project has no Ember application yet, one is generated in a folder named frontend, and the README notes that this creates no git repository of its own:

bash
$ ember new frontend --skip-git

The problem this gem solves is the gap between a Rails backend and an Ember CLI front end that are developed, served, and deployed as separate things. The README lists what that gap costs: the two halves drift on different toolchains, the browser has to be told to allow cross origin requests, and integration tests have to stitch two systems together instead of exercising one stack. The approach taken here is to let Rails own the Ember application. The gem knows where each Ember app lives on disk, builds it, and serves the result from a route inside the Rails router, so a request for a page and a request for JSON come out of the same origin and the same process. That has consequences for the developer and for deployment. In development, a change to the Ember source is picked up without a separate frontend server to start. In production, one git push moves the whole suite of applications. The design deliberately keeps the two build systems in their own lanes: the gem does not replace the Ember CLI, it drives it.

The initializer is where every Ember app gets registered

Everything begins with a generated initializer. Running the generator writes a file into config/initializers, and the default contents point at an Ember app sitting in a folder named after the app in the Rails root. Each registered app takes a name, and the options attached to it decide how the build is invoked. The path option overrides where the app lives, which matters when the Ember project is kept in a separate directory. The silent option quiets the build, and it is not one flag but two, because the project supports two build pipelines: a classic application is built with ember build --silent, while a Vite based one is built with vite build --logLevel error, which keeps the errors and drops the progress output. The package_manager option decides which Node package manager installs the app dependencies, and it accepts :npm as the default plus :yarn and :pnpm, with npm_path, yarn_path, or pnpm_path for an executable that is not on the $PATH. The dev_server option configures the Vite development server for Vite based apps in development, and passing false opts out of it entirely, while a Hash lets you set host, port, and timeout.

mount_ember_app decides which requests reach the Ember app

Registration alone does not serve anything. A route has to point Rails at the front end, and the README shows that as a single call inside the usual routes draw block. The path option says which URL prefix belongs to the Ember application, and the route also handles child routes below that prefix, so mounting at a nested path still captures requests to deeper URLs. Two defaults sit behind the call: the controller is ember_cli/ember and the action is index. There is a caveat that catches people out, and it is written in a note rather than the main description. The route only applies to requests whose format is :html, which means a request without an Accept: text/html header skips the route entirely and receives the raw index.html straight from the asset handler. The note calls out curl and most health checkers as examples of clients that send no such header, so a monitoring probe can behave differently from a browser for reasons that have nothing to do with your Ember code. If you mount a custom path, the README also requires updating the rootURL in the frontend's config/environment.js.

Vite apps get a dev server that Rails starts and stops

This is where the gem does its most interesting work. Classic Broccoli based applications, meaning those generated with ember-cli below 6.8, are built with ember build --watch in development, so edits to the Ember source show up without a rebuild command. Vite based applications take a different route. Instead of a watching build, the gem starts the Vite development server, the same one the application's npm start script runs, on the first request, waits for it to accept connections, and shuts it down when Rails exits. Rails still renders index.html, but it reads that document from the development server rather than from disk, and the root relative URLs inside it are rewritten so the browser loads the application modules from the server, including Vite's hot reload client. The rewriting covers every tag the server emits, since Vite externalizes even the inline bootstrapping script in the document. The result is hot reloading of the Ember application without a page reload and without restarting Rails. Relative assets such as an image referenced from a template are still requested from Rails, which proxies them onward to the development server. By default the server listens on an available port on 127.0.0.1, and the gem waits up to 30 seconds for it to start, with the host, port, and timeout values overridable in the initializer.

Rake tasks are the seam between the two test suites

Because the Ember application is built by the gem rather than by a separate front end pipeline, the build and test steps have to be reachable from the Ruby side. The README documents three rake tasks for this. The install task installs the dependencies of the Ember applications, and it is the one you run after wiring everything up, once the app dependencies need to be present before the first build. The compile task compiles the Ember applications. The test task executes the Ember test suite. The integration point is the test task: if you drive your test suite with Rake, the Ember test task has to be a dependency of the Ruby test task, or the Ember tests simply never run and nothing tells you. The example configures a bare rake invocation to run both RSpec and the Ember suite by making the default task depend on the spec task and the ember:test task together. That one line in a Rakefile is the whole contract between the two frameworks, and it is also the easiest thing to forget when a new developer joins.

The addon is required for Broccoli apps and forbidden for Vite apps

There is a hard version boundary in the setup, and it is not symmetric. If your Ember application uses the classic Broccoli based build, generated with ember-cli below 6.8, the ember-cli-rails-addon has to be installed inside the Ember project, and the addon's MAJOR and MINOR version must match the gem's MAJOR and MINOR version. The README spells out the mechanics: for a 0.6.x version of the gem you specify ~> 0.6.0 in the Ember application's package.json devDependencies, so the two packages move together and a mismatch is prevented by the version constraint. If your application uses the Vite based build, generated with ember-cli 6.8 or later, the addon must not be installed, because the README states it is incompatible with the Vite based build and that the gem does not require it there. That leaves an awkward middle ground for teams with two Ember applications of different generations, since a single package.json constraint cannot satisfy both, and the constraint applies per application rather than to the Rails side. The README also states that the gem supports EmberCLI 1.13.13 and later, which is a floor rather than a ceiling, and the version you install decides which of these paths applies.

The alternative is two servers and an explicit CORS policy

The contrast that makes this gem's design readable is the split setup it exists to avoid. Running the Ember CLI dev server on its own port and pointing a Rails API at it from a separate host means two processes, two toolchains, a browser talking to two origins, and a deployment pipeline that has to move two artifacts instead of one. The CORS requirement is the part that usually surfaces first, and the README lists avoiding those gotchas as a reason to combine the two. The real difference in approach is who owns the build output. Here, Rails owns it: the route serves the document, the asset pipeline serves the files, and the dev server is a child process with a lifecycle tied to the Rails process. In the split setup the browser owns it, because the browser is the only thing that talks to both servers. The trade is control. The gem gives you a single origin, a single deploy, and a single test command, at the cost of the tight coupling the addon version rule and the Vite path already show, since a change in the front end build system can require a matching change on the Ruby side. The repository also ships CHANGELOG.md, UPGRADING.md, CONTRIBUTING.md, and SECURITY.md, which are the files to read before bumping the gem in a long lived application.

Recent commits on main, but release tags that stop in 2017

The maintenance picture here needs two separate facts. The repository is not archived, and the last push was on 2026-10-19, which is two days before the date this article was written. The listed releases tell a different story: v0.8.6 from 2017-06-09, v0.8.5 from 2017-05-12, and v0.8.4 from 2017-01-27, so the newest tag is nearly a decade old. The gap is not a contradiction, because the README describes support for the Vite based build and for ember-cli 6.8 or later, which did not exist in 2017, and it documents a version rule for the addon using a 0.6.x example, so work on main has clearly continued past the last tag. What that means for a reader is concrete: do not read the release list as the current feature set, and do not assume a 2017 tag is what you would install. Check the gem version resolved in your lockfile against the README on the branch you intend to use, and read UPGRADING.md for the change between versions. The licence is MIT, which permits use, modification, and redistribution including in closed source work, provided the copyright notice and licence text travel with the code, and it places no requirement on the applications you build with it.

Editorial conclusion

Adopt ember-cli-rails if you already run Rails and want one process, one domain, and one deploy for an Ember CLI front end, and accept that the classic Broccoli path still depends on a separate addon whose major and minor versions must track the gem. Do not adopt it for a Vite based app expecting the addon, since the README calls that combination incompatible, and do not treat the 2017 release tags as the current state of the project. Before you start, verify that the version of the gem in your Gemfile still declares support for your ember-cli version, because the README only guarantees EmberCLI 1.13.13 and later while the maintained branches in the repository have moved well past the last tagged release.

Frequently asked questions

How do I install ember-cli-rails in a Rails app?

Add the gem to your Gemfile and run bundle install, then generate the initializer with rails generate ember:init. The generated file registers each Ember app through EmberCli.configure, and a rake ember:install task installs the Ember side dependencies.

Do I need the ember-cli-rails-addon for a Vite based Ember app?

No. The README states the addon is incompatible with the Vite based build used by apps generated with ember-cli 6.8 or later, and that ember-cli-rails does not require it there. The addon is only needed for the classic Broccoli based build, and then its MAJOR and MINOR version must match the gem's.

How do I run the Rails and Ember test suites together?

The gem exposes a rake ember:test task that runs the Ember test suite. The README instructs you to make your Ruby test task depend on ember:test, for example by configuring the default task to depend on both spec and ember:test.

How does ember-cli-rails serve a Vite based app in development?

It starts the Vite development server on the first request, waits for it to accept connections, and shuts it down when Rails exits. Rails renders index.html but reads it from the development server and rewrites the root relative URLs in it, so modules and the hot reload client load directly from Vite.

Why does my request to the mounted Ember route return raw index.html?

The route only applies to requests with format: :html, so a request sent without an Accept: text/html header skips it and is served by the asset handler instead. The README notes that curl and most health checkers send no such header, which is why a probe can behave differently from a browser.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tricknotes/ember-cli-rails on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tricknotes-ember-cli-rails.svg)](https://hysenlabs.com/projects/tricknotes-ember-cli-rails)

Was this useful?