18

I can use Angular 2 to create basic front-end applications and can use python to create back-ends with endpoints on Google App engine. I can't however seem to figure out how to put the two together and deploy them with the cloud SDK.

Here is a basic example where I can't even host a simple angular2 app with no back-end calls on GAE. I have taken the dist folder after building with angular2 CLI and tried to connect to it with the app.yaml file. It seems to work in the browser developer (dev_appserver.py app.yaml) although I get some 404 errors in SDK with the GET requests to do with my index.html file I think. I then create a blank index.yaml file and try to deploy it but get a 404 Error at the appspot.com location. This is the app.yaml file:

application:
version:
runtime: python27
threadsafe: true
api_version: 1

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: (.*)/
  static_files: dist\1/index.html
  upload: dist

- url: (.*)
  static_files: dist\1
  upload: dist

I really have no idea what I am doing wrong. Do I need some kind of a main.application python back-end to connect to the dist files or? Do I need to include node modules or some other kind or files from Angular2? Any help would be massively appreciated! Thanks

Nicholas
  • 1,003
  • 1
  • 12
  • 31

7 Answers7

25

For the latest versions of Angular 4 and App Engine I built the following:

service: stage
runtime: python27
api_version: 1
threadsafe: true

skip_files:
- ^(?!dist)  # Skip any files not in the dist folder

handlers:
# Routing for bundles to serve directly
- url: /((?:inline|main|polyfills|styles|vendor)\.[a-z0-9]+\.bundle\.js)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/\1
  upload: dist/.*

# Routing for a prod styles.bundle.css to serve directly
- url: /(styles\.[a-z0-9]+\.bundle\.css)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/\1
  upload: dist/.*

# Routing for typedoc, assets and favicon.ico to serve directly
- url: /((?:assets|docs)/.*|favicon\.ico)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/\1
  upload: dist/.*

# Any other requests are routed to index.html for angular to handle so we don't need hash URLs
- url: /.*
  secure: always
  redirect_http_response_code: 301
  static_files: dist/index.html
  upload: dist/index\.html
  http_headers:
    Strict-Transport-Security: max-age=31536000; includeSubDomains
    X-Frame-Options: DENY

Looking for feedback on how this could be improved.

Rob
  • 3,428
  • 2
  • 28
  • 38
  • 1
    By far the best answer at this time. If I find and improvements I ll come back. Thanks – Jimmy Kane Jul 14 '17 at 19:02
  • @Rob I will set this as the accepted answer. Do you ever incorporate this, or any other, type of [sever-side rendering](https://github.com/clbond/angular-ssr) into the deployment? – Nicholas Jul 15 '17 at 02:16
  • Nope, I was building a web app behind login so I wasn't worried about search engines. – Rob Jul 15 '17 at 19:17
  • I tried this, but now all of a sudden instead of my js/css file the `index.html` file is being served – Armen Vardanyan Mar 25 '18 at 16:42
  • Do this work with Angular 6? I get a "Error: Not Found The requested URL / was not found on this server." error. – Venkata Koppaka May 20 '18 at 06:09
15

I now update the handlers in my app.yaml file to look like this for static uploads to the google cloud platform. There was issues with Angular Router if the regular expression wasn't like this. Dist folder is output from angular cli ng build:

handlers:
- url: /favicon.ico
  static_files: dist/favicon.ico
  upload: dist/assets/favicon.ico

- url: /(.*\.(gif|png|jpg|css|js)(|\.map))$
  static_files: dist/\1
  upload: dist/(.*)(|\.map)

- url: /(.*)
  static_files: dist/index.html
  upload: dist/index.html

UPDATE:

For production ng build --prod, this is how my app.yaml file would look:

runtime: python27
threadsafe: true
api_version: 1

handlers:
- url: /(.*\.(gif|png|jpeg|jpg|css|js|ico))$
  static_files: dist/\1
  upload: dist/(.*)
- url: /(.*)
  static_files: dist/index.html
  upload: dist/index.html

I would add any other file types in the dist folder to the regex grouping characters in the first handler: (gif|png|jpeg|jpg|css|js|ico)

Nicholas
  • 1,003
  • 1
  • 12
  • 31
  • Is there any documentation to refer? how did you figure it out? – igsm Jan 21 '18 at 23:27
  • why are we using python27 runtime for nodejs platform ? – str028 Dec 05 '19 at 02:24
  • at the time of this q&a all my applications backend apis on app engine were written in python27. If i'm hosting an angular site on it's own now, ive started using nodejs for server side rendering: https://www.youtube.com/watch?v=VS0zsXvDJ08 – Nicholas Dec 06 '19 at 09:49
9

For Angular 6, the file structure changed somewhat. The following is based on @Rob's answer, but updated for an Angular 6 with a service-worker enabled. Be sure to replace "my-app" with the folder name of your app.

service: stage
runtime: python27
api_version: 1
threadsafe: true

skip_files:
- ^(?!dist)  # Skip any files not in the dist folder

handlers:
# Routing for bundles to serve directly
- url: /((?:runtime|main|polyfills|styles|vendor)\.[a-z0-9]+\.js)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/my-app/\1
  upload: dist/my-app/.*

# Routing for bundle maps to serve directly
- url: /((?:runtime|main|polyfills|styles|vendor)\.[a-z0-9]+\.js\.map)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/my-app/\1
  upload: dist/my-app/.*

# Routing for a prod styles.bundle.css to serve directly
- url: /(styles\.[a-z0-9]+\.css)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/my-app/\1
  upload: dist/my-app/.*

# Routing for typedoc, assets, and favicon.ico to serve directly
- url: /((?:assets|docs)/.*|favicon\.ico)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/my-app/\1
  upload: dist/my-app/.*

# Routing for service worker files serve directly
- url: /(manifest\.json|ngsw\.json|ngsw-worker\.js|safety-worker\.js|worker-basic\.min\.js|ngsw_worker\.es6\.js\.map)
  secure: always
  redirect_http_response_code: 301
  static_files: dist/my-app/\1
  upload: dist/my-app/.*

# Any other requests are routed to index.html for angular to handle so we don't need hash URLs
- url: /.*
  secure: always
  redirect_http_response_code: 301
  static_files: dist/my-app/index.html
  upload: dist/my-app/index\.html
  http_headers:
    Strict-Transport-Security: max-age=31536000; includeSubDomains
    X-Frame-Options: DENY
Kevin Dufendach
  • 118
  • 1
  • 5
2

It looks like your regular expression match is in the wrong spot. Try this format:

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico
- url: /
  static_files: dist/index.html
  upload: dist/index.html
- url: /(.*)
  static_files: dist/\1
  upload: dist/(.*)

This comes from testing and some oddities we encountered while creating the Static Hosting tutorial on App Engine.

BrettJ
  • 6,676
  • 1
  • 20
  • 26
1

Replace your app.yaml with the following. It will work!

application: you-app-name-here
version: 1
runtime: python
api_version: 1

default_expiration: "30d"

handlers:
- url: /(.*\.(appcache|manifest))
  mime_type: text/cache-manifest
  static_files: static/\1
  upload: static/(.*\.(appcache|manifest))
  expiration: "0m"

- url: /(.*\.atom)
  mime_type: application/atom+xml
  static_files: static/\1
  upload: static/(.*\.atom)
  expiration: "1h"

- url: /(.*\.crx)
  mime_type: application/x-chrome-extension
  static_files: static/\1
  upload: static/(.*\.crx)

- url: /(.*\.css)
  mime_type: text/css
  static_files: static/\1
  upload: static/(.*\.css)

- url: /(.*\.eot)
  mime_type: application/vnd.ms-fontobject
  static_files: static/\1
  upload: static/(.*\.eot)

- url: /(.*\.htc)
  mime_type: text/x-component
  static_files: static/\1
  upload: static/(.*\.htc)

- url: /(.*\.html)
  mime_type: text/html
  static_files: static/\1
  upload: static/(.*\.html)
  expiration: "1h"

- url: /(.*\.ico)
  mime_type: image/x-icon
  static_files: static/\1
  upload: static/(.*\.ico)
  expiration: "7d"

- url: /(.*\.js)
  mime_type: text/javascript
  static_files: static/\1
  upload: static/(.*\.js)

- url: /(.*\.json)
  mime_type: application/json
  static_files: static/\1
  upload: static/(.*\.json)
  expiration: "1h"

- url: /(.*\.m4v)
  mime_type: video/m4v
  static_files: static/\1
  upload: static/(.*\.m4v)

- url: /(.*\.mp4)
  mime_type: video/mp4
  static_files: static/\1
  upload: static/(.*\.mp4)

- url: /(.*\.(ogg|oga))
  mime_type: audio/ogg
  static_files: static/\1
  upload: static/(.*\.(ogg|oga))

- url: /(.*\.ogv)
  mime_type: video/ogg
  static_files: static/\1
  upload: static/(.*\.ogv)

- url: /(.*\.otf)
  mime_type: font/opentype
  static_files: static/\1
  upload: static/(.*\.otf)

- url: /(.*\.rss)
  mime_type: application/rss+xml
  static_files: static/\1
  upload: static/(.*\.rss)
  expiration: "1h"

- url: /(.*\.safariextz)
  mime_type: application/octet-stream
  static_files: static/\1
  upload: static/(.*\.safariextz)

- url: /(.*\.(svg|svgz))
  mime_type: images/svg+xml
  static_files: static/\1
  upload: static/(.*\.(svg|svgz))

- url: /(.*\.swf)
  mime_type: application/x-shockwave-flash
  static_files: static/\1
  upload: static/(.*\.swf)

- url: /(.*\.ttf)
  mime_type: font/truetype
  static_files: static/\1
  upload: static/(.*\.ttf)

- url: /(.*\.txt)
  mime_type: text/plain
  static_files: static/\1
  upload: static/(.*\.txt)

- url: /(.*\.unity3d)
  mime_type: application/vnd.unity
  static_files: static/\1
  upload: static/(.*\.unity3d)

- url: /(.*\.webm)
  mime_type: video/webm
  static_files: static/\1
  upload: static/(.*\.webm)

- url: /(.*\.webp)
  mime_type: image/webp
  static_files: static/\1
  upload: static/(.*\.webp)

- url: /(.*\.woff)
  mime_type: application/x-font-woff
  static_files: static/\1
  upload: static/(.*\.woff)

- url: /(.*\.xml)
  mime_type: application/xml
  static_files: static/\1
  upload: static/(.*\.xml)
  expiration: "1h"

- url: /(.*\.xpi)
  mime_type: application/x-xpinstall
  static_files: static/\1
  upload: static/(.*\.xpi)

# image files
- url: /(.*\.(bmp|gif|ico|jpeg|jpg|png))
  static_files: static/\1
  upload: static/(.*\.(bmp|gif|ico|jpeg|jpg|png))

# audio files
- url: /(.*\.(mid|midi|mp3|wav))
  static_files: static/\1
  upload: static/(.*\.(mid|midi|mp3|wav))  

# windows files
- url: /(.*\.(doc|exe|ppt|rtf|xls))
  static_files: static/\1
  upload: static/(.*\.(doc|exe|ppt|rtf|xls))

# compressed files
- url: /(.*\.(bz2|gz|rar|tar|tgz|zip))
  static_files: static/\1
  upload: static/(.*\.(bz2|gz|rar|tar|tgz|zip))

# index files
- url: /(.+)/
  static_files: static/\1/index.html
  upload: static/(.+)/index.html
  expiration: "15m"

- url: /(.+)
  static_files: static/\1/index.html
  upload: static/(.+)/index.html
  expiration: "15m"

# site root
- url: /
  static_files: static/index.html
  upload: static/index.html
  expiration: "15m"
Ivar Reukers
  • 7,188
  • 8
  • 47
  • 90
Nipun Madan
  • 140
  • 1
  • 9
  • Hi @Ivaro18 I now see that this is a good standard app.yaml file to use for my angular2 projects. How would you change it to get routing to work on appspot refreshes? I posted another question but no help yet: [link](http://stackoverflow.com/questions/39944208/app-engine-on-page-refresh-gives-404-for-my-angular2-project). Any help would be much appreciated – Nicholas Oct 12 '16 at 07:39
  • @Nipun Madan referring Nipun, I only replaced the URL with the actual code – Ivar Reukers Oct 12 '16 at 07:41
0

first build your angular project by running the following command

--ng build prod

the after the build is done create an app.yaml file in the root folder of your project and paste the following code:

# [START runtime]
runtime: python27
threadsafe: yes
# [END runtime]

handlers:

- url: /(.+)
 static_files: dist/\1
 upload: dist/(.*)

- url: /
 static_files: dist/index.html
 upload: dist/index.html

# Temporary setting to keep gcloud from uploading not required files for 
deployment
skip_files:
- ^node_modules$
- ^app\.yaml
- ^README\..*
- \.gitignore
- ^\.git$
- ^grunt\.js
- ^src$
- ^e2e$
- \.editorconfig
- ^karma\.config\.js
- ^package\.json
- ^protractor\.conf\.js
- ^tslint\.json

after this run:

gcloud app deploy
Shubham Yadav
  • 33
  • 1
  • 4
0

If you're using custom fonts, you may use this template:

handlers:
  # Routing for bundles to serve directly
  - url: /(.*\.(gif|png|jpeg|jpg|css|js|ico))$
    static_files: dist/\1
    upload: dist/(.*)

  - url: /assets/fonts/(.*\.(eot|woff|woff2|svg))$
    static_files: dist/assets/fonts/\1
    upload: dist/assets/fonts/(.*)

  - url: /.*
    static_files: dist/index.html
    upload: dist/index.html
Joe Sorocin
  • 12,670
  • 3
  • 10
  • 46