Skip to content

JS Deploy

Intro

  • After your javascript app is built, it needs to be hosted by publicly accessible webserver
  • Simplest solution – get some free hosting (even enos.itcollege.ee/~homefolder should be good enough for that)
  • Don’t forget to setup your servers root/home path – otherwise script inclusion paths are wrong. Routing also gets confused.
  • If you want to use full html5 history mode for url, server needs some configuration (/invoice/client/5 vs /#invoice/client/5)

Docker 1

  • Modern solution – deploy to docker

Types

  • Commonly nginx image is used as base for js apps
  • Two possible nginx baseimages to use

    • nginx:latest
    • nginx:alpine
  • Alpine – special ultra-lightweight linux based image. Has some compatibility problems (uses different libc) on some rare cases.

  • Alpine linux image is ca 5X smaller
    • https://alpinelinux.org

Docker 2

  • Minimal Dockerfile
  • Baseimage nginx:latest

    • Copy all files and folders from ./dist/ to images html content folder (build your JS app in production mode first)
  • Build the container
    > docker build -t js-nginx:latest .

  • Start the container (map local port 8080 to containers port 80)
    > docker run -d -p 8080:80 js-nginx:latest
  • List running containers
    > docker ps
  • Stop container (with id tacken from docker ps)
    > docker container stop 425d351d2f35

Dockerfile

1
2
FROM nginx:latest
COPY ./dist /usr/share/nginx/html

Docker 3

  • Supporting html5 history mode
  • JS routers typically have two modes – using full urls or hashtags
  • Problem with full urls – when new session is started with full url, server will respond with 404 – page not found. Because we actually only have one single page /index.html
  • Servers need some configuration to rewrite urls to host the same page regardless of specified url (when exact match is not found).
  • Nginx configuration (partial)
1
2
3
4
5
6
7
http {
    server {
        location / {
            try_files $uri $uri/ /index.html;
        }
    }
}

Docker 4

  • Typically for testing we need to deploy several JS apps side-by-side
    • This allows us to use only one container
  • Configure base paths in JS apps correctly
  • Copy every JS app into separate directory inside docker image
  • Copy nginx.conf also into container
1
2
3
FROM nginx:latest
COPY ./vue-app/dist /usr/share/nginx/html/vue-app
COPY nginx.conf /etc/nginx/nginx.conf

Vue 3 in subdir

  • Add .env.production
1
BASE_URL=/vue-app
  • Modify vite.config.ts, configure base
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig(({ mode }) => {
    const env = loadEnv(mode, process.cwd(), '')
    return {
        plugins: [
            vue(),
        ],
        base: env.BASE_URL,
        resolve: {
            alias: {
                '@': fileURLToPath(new URL('./src', import.meta.url))
            }
        }
    }
})

React in subdir (classical)

  • Add .env.production
1
PUBLIC_URL=/react-app
  • modify router
1
2
3
4
5
6
7
8
ReactDOM.render(
    <Router basename={process.env.PUBLIC_URL}>
        <React.StrictMode>
            <App />
        </React.StrictMode>
    </Router>,
    document.getElementById('root')
);

nginx.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
worker_processes 1;
events {
    worker_connections 1024;
}
http {
    server {
        listen 80;
        server_name localhost;

        root /usr/share/nginx/html;
        index index.html index.htm;
        include /etc/nginx/mime.types;

        gzip on;
        gzip_min_length 1000;
        gzip_proxied expired no-cache no-store private auth;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;


        location /react-app/ {
            # nextjs config to resolve /foo => /foo.html
            try_files $uri $uri.html /react-app/index.html =404;
        }

        location /vue-app/ {
            try_files $uri $uri/ /vue-app/index.html;
        }
    }
}

next.js spa - no serverside

  • Add .env.production for npm run build
1
BASE_URL=/react-app
  • Add .env.development for local npm run dev
1
BASE_URL=

or check in code - when you use process.env.BASE_URL to handle undefined correctly.

1
const BASE_URL = process.env.BASE_URL || '';

Modify next.config.mjs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/** @type {import('next').NextConfig} */

const base_path = process.env.BASE_URL;

const nextConfig = {
    output: 'export',
    distDir: 'dist',
    basePath: base_path,
};

export default nextConfig;

When using the next/image component, you will need to add the basePath in front of src.

1
2
3
4
            <Image
              src={process.env.BASE_URL +  "/next.svg"}
              alt="Vercel Logo"
            />

next.js in docker - needs node.js (standalone, includes ssr)

https://github.com/vercel/next.js/tree/canary/examples/with-docker

Docker 5

Add default /index.html. With links into different JS app areas.