3 ways to host a single-page application on Google App Engine πŸ’‘

Hung Vu - Feb 18 '22 - - Dev Community

This is more about describing my personal development and debugging experience, less of a step-by-step tutorial on the topic. Correction to any of the errors is welcome!

Problem statement πŸ€

I have a React application with NestJS as the backend. Google App Engine is my target deployment environment. In my Google App Engine deployment article, I successfully made a dummy NodeJS application deployed to App Engine, however, that did not include the front end. Now, how should I host my single-page app there?

My folder structure is as followed.

- root
   - .github/workflows
   - front-end
   - back-end
Enter fullscreen mode Exit fullscreen mode

What are the approaches? πŸ€”

There are 3 ways that I'm aware of.

  1. Using two App Engine services and dispatch.yaml.
  2. Using one App Engine service to host both React and NestJS apps.
  3. Using one App Engine service to host NestJS app and another server to host static React files.

Using dispatch.yaml πŸ”‘

This can work when the back end and front end are loose couplings, and I want to utilize Google Identity Aware Proxy.

What is dispatch.yaml?

It is a configuration file for an App Engine service. The term service here resembles a Compute Engine instance (not a direct equivalent though). A dispatch.yaml file overrides routing rules at an App Engine service level. Meaning, it is before a request reaches my API.

Usage

Assuming my front-end is deployed to service react at react.uc.r.appspot.com/, while back-end is deployed to service api at api.uc.r.appspot.com/.

# dispatch.yaml
# Put this one in my 'front-end' folder
# Assuming this is a configuration for service "react"
# All requests to 'react.uc.r.appspot.com/api/*' are routed to 'api.uc.r.appspot.com/'
dispatch:
  - url: "react.uc.r.appspot.com/api/*"
    service: api
Enter fullscreen mode Exit fullscreen mode

Advantages and disadvantages

  1. Advantages
    • No need to worry about same-origin issues (e.g., CORS), because the resources are served via the same domain.
    • Can separate the deployment of front-end and back-end (e.g., Creating a workflow that runs when only when files at a path are updated). Inherently increase redundancy and resiliency.
    • Reduce server load.
    • Both can be protected by Google Identity Aware Proxy.
  2. Disadvantages
    • Not applicable when a front end is heavily dependent on the back end, this will be further discussed in the next approach.
    • Might increase the cost since there are 2 App Engine services online.
   # Sample of CORS error
   Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ...
Enter fullscreen mode Exit fullscreen mode

A back end stores React assets and statically serves them πŸ”‘

Unlike the first approach, this one helps when the front end requires a resource from the back end at the first load. For example, my React app needs a CSRF token to work with my API. The CSRF token must be available when a user first reaches my website. To do so, my React app has to be served as static files from the NodeJS back end.

Usage

The instruction below applies to the NestJS framework. My application is deployed to only one domain back-end.uc.r.appspot.com.

  • In my GitHub workflow, I copied the static React files to the back-end folder after React build process is finished.
  # CI/CD pipeline
  steps: 
    ...
    - name: Build front-end
       ...
    - name: Copy front-end built to back-end folder
      working-directory: front-end
      run: |
        mkdir -p ../back-end/assets
        cp -R build ../back-end/assets
Enter fullscreen mode Exit fullscreen mode
  • In nest-cli.json, use compilerOptions to copy React asset to NestJS dist build.
  {
    ...
    "soruceRoot": "src",
    "compilerOptions": {
      "assets": ["../assets/"]
    }
  }
Enter fullscreen mode Exit fullscreen mode
  • Deploy only back-end folder to App Engine. React files are included and can be statically served.

Advantages and Disadvantages

  1. Advantages

    • Reduce cost as there is only one App Engine service online.
    • No need to worry about same-origin issues (e.g., CORS), because the resources are served via the same domain.
    • Can be protected by Google Identity Aware Proxy.
    • Work with a tight coupling web application.
  2. Disadvantages

    • Increase server load as the whole single-page application lives on the same App Engine service.
    • Cannot separately deploy back end and front end. A change in one place requires a whole new deployment. Inherently decrease redundancy and resiliency.

Statically serve, but React files are hosted on another server πŸ”‘

A common strategy, using (e.g., Google Cloud Storage Buckets) which is optimized for hosting static files can greatly improve performance. You can further fine-tune the server to other aspects too.

Usage

It's essentially the same as the second approach, but with different configurations depending on situations, so I don't dive into this. An example of serving static files from Google Cloud Storage Buckets using App Engine can be found here (official Google Cloud Documents).

Advantages and Disadvantages

  1. Advantages

    • Better redundancy and resiliency.
    • Better performance and is easier to fine-tune.
    • Can separate the deployment of front-end and back-end (e.g., Creating a workflow that runs when only when files at a path are updated). Inherently increase redundancy and resiliency.
  2. Disadvantages

    • Might face same-origin issues (e.g., CORS) because files are hosted on another domain.
    • Might not be able to activate Google Identity Aware Proxy for the front end.
    • Work with a tight coupling web application.
    • You need to have experience in using another static file hosting service, and using a different platform can decentralize your control.

Wrap up πŸ€

In summary, I can

  1. Using 2 App Engine services if my React app and back end are loose couplings.
  2. Using App Engine to host back end, while using another service to host static files when I want even better performance, redundancy, resiliency.
  3. Using 1 App Engine to host the whole single-page application if it is tightly coupled.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .