Deploying a React Native Web project with CircleCI

  1. Generate a private & public SSH key for the CI server.
    1. Paste the private key into circle CI.
    2. Grab the key fingerprint from circle CI for later.
    3. Paste the public key into ~/.ssh/authorized_keys/ on the host.
  2. Create a .circleci folder in the root of your project with a config.yml file underneath.
  3. Adjust the following circle CI config as necessary. The file:
    1. loads a node environment
    2. allows the fingerprint supplied to connect
    3. restores cached node_modules and yarn cache
    4. runs yarn install
    5. saves caches
    6. adds expo-cli – currently does not cache, so will play with that further
    7. builds the project (into web-build/ subfolder)
    8. uploads the project via scp to the remote server
version: 2
    working_directory: ~/web
      - image: circleci/node:8
      - add_ssh_keys:
            - "<the ssh fingerprint from above>"
      - checkout
      - restore_cache:
          key: yarn-v1-{{ checksum "yarn.lock" }}-{{ arch }}

      - restore_cache:
          key: node-v1-{{ checksum "package.json" }}-{{ arch }}

      - run: yarn install

      - save_cache:
          key: yarn-v1-{{ checksum "yarn.lock" }}-{{ arch }}
            - ~/.cache/yarn

      - save_cache:
          key: node-v1-{{ checksum "package.json" }}-{{ arch }}
            - node_modules

      - run: yarn global add expo-cli
      - run: CI=false && yarn web-build && CI=true
      - run: scp -o StrictHostKeyChecking=no -r ./web-build/* user@host-server-ip-address:/var/www/path/to/app/

As the project wears on, I’ll add some unit testing and end to end testing to this pipeline.

Footnote: Deploying expo build:web to a subfolder

I had a hell of a time finding the right incantation to get this working. There’s different advice everywhere.

The solutions are not:

  • homepage field in package.json
  • Anything in app.json
  • Any command line flags/switches in expo build

The solution is to customize expo’s webpack file, to specify that there is an output subdirectory.

  • Run expo customize:web and choose to export webpack.config.js
  • Specify the public path required. e.g. my webpack.config.js now looks like:
const createExpoWebpackConfigAsync = require("@expo/webpack-config");

module.exports = async function(env, argv) {
  const config = await createExpoWebpackConfigAsync(env, argv);
  // Customize the config before returning it.

  config.output.publicPath = "/app/";
  return config;
  • Note the addition of config.output.publicPath