Web Server Cache Issue with Ionic App

Hi All,

In addition to placing my Ionic app on the App Store, I am also hosting it on a web server. However, I am unsure whether the issue I’m experiencing is related to the web server or if something can be done in Ionic to expedite loading on the web server.

One issue I noticed is that when I upload the www folder to the web server, I need to clear my PC cache and internet files every time; otherwise, the updated app does not appear, and the previous version is still loaded.

I believe this is related to caching, either on the server or in the browser. If it’s a browser issue, we can’t expect all users to clear their cache manually every time we make an update. It seems that the browser does not recognize the changes.

Is there something we can do, such as adding a tag to each page, to ensure that browsers acknowledge updates and always download the latest version?

Looking forward to your suggestions.

What you are using to build your app? If you are using Vite, it adds a hash to the filenames so the cache gets busted on new builds. The only file that the browser would cache would be the index.html which you could configure the cache TTL on your server or set index.html to never cache like Cache-Control: no-store.

You could also implement something that alerts the user they are running an older version and need to refresh to load the most recent.

I am just using Ionic Build. Is there other ways to prepare of a we server.

Also to inform users would not be idle to ask them to keep clearing the cache.

I would prefer if we could do it automatically

Well, ionic build doesn’t actually do the building of the web app. It hands off the build to whatever underlying stack you are using. Angular, the Angular CLI, Vue the Vue CLI or Vite, and similarly with React.

I am not familiar with Angular or React, but I would assume any stack these days should be creating unique file names per build so the cache gets busted.

I already gave you a solution to handle index.html.

In regards to -

that was suggested to handle when users leave your app open in the web browser for days on end, they never refresh getting a new index.html file. So, alerting them that there is a new version is a good option.

Yes i am using Angular

So ideally if I understood it correctly the index.html should have some changes inside it so that browser does not depend on cacheed version

I just looked at an Angular project that I’ve worked on (v17) and a production build has hashes in the file names. The index.html references the JS files with hashes. So, every time a build is created, the index.html gets updated with the new hashes.

I would guess that your problem is just with your index.html file being cached.

Try adding cache-busting techniques like appending a version query (?v=1.2.3) to file URLs or setting cache-control headers in your server (Cache-Control: no-cache, no-store, must-revalidate). This should force browsers to load the latest version.

Corrected Version

Thanks,

Regarding Index.html, every time I run an Ionic build or use ionic build --prod, it always generates the same HTML file. How do I ensure there are new hash in the file?

Regarding the server, we are doing the following via the .htaccess file:

# Enable URL rewriting
RewriteEngine On

# Redirect all HTTP requests to HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Allow access to existing files and directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Redirect all other requests to index.html
RewriteRule ^ index.html [L]

<IfModule mod_headers.c>
Header set Cache-Control "private"
</IfModule>

In regards to your index.html file, you aren’t seeing any hashes for JS file includes in the production build? If you see hashes, they aren’t necessarily going to change if you just rebuild without changing any code. Can you share what the file looks like, both the dev/raw version and the built/compiled version?

And for your .htaccess, I think you want the following instead of private (assuming that you have right syntax):

Header set Cache-Control "no-store"


This is how the file names look

I meant the actual contents of your index.html file.

For example, my production build includes JS and CSS with hashed file names (Vue & Vite):

<script type="module" crossorigin src="/assets/index-DkNNhdK7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C_GInD0z.css">

In an Angular 17 project something like this (looking in dist or www folder):

<script src="runtime.f5a50f1e54b6977d.js" type="module"></script>
<script src="polyfills.c3a9e4b87df5be46.js" type="module"></script>
<script src="main.a0fe3c2c74a4dd90.js" type="module"></script>

This is an example of my file. It does not look like it is being hashed.

`

Do we need to enable this hash option for file names,

Doesn’t look like your file came through.

Either way, the Angular 17 app I am looking at has "outputHashing": "all" in it’s angular.json file under projects -> app -> architect -> build -> configurations -> production.

This is what i have


{
  "$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json",
  "version": 1,
  "defaultProject": "app",
  "newProjectRoot": "projects",
  "projects": {
    "app": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "aot": true,
            "outputPath": "www",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              },
              {
                "glob": "**/*.svg",
                "input": "node_modules/ionicons/dist/ionicons/svg",
                "output": "./svg"
              }
            ],
            "styles": [
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "node_modules/swiper/swiper-bundle.min.css",
              {
                "input": "src/theme/variables.scss"
              },
              {
                "input": "src/global.scss"
              }
            ],
            "scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "node_modules/bootstrap/dist/js/bootstrap.min.js"
            ],
            "allowedCommonJsDependencies": [
              "apexcharts",
              "highcharts",
              "moment",
              "quill",
              "quill-delta"
            ]
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": {
                "scripts": true,
                "styles": {
                  "minify": true,
                  "inlineCritical": false
                },
                "fonts": true
              },
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                }
              ]
            },
            "ci": {
              "budgets": [
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                }
              ],
              "progress": false
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "app:build:production"
            },
            "ci": {
              "progress": false
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "app:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "karmaConfig": "src/karma.conf.js",
            "styles": [],
            "scripts": [],
            "assets": [
              {
                "glob": "favicon.ico",
                "input": "src/",
                "output": "/"
              },
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "/assets"
              }
            ]
          },
          "configurations": {
            "ci": {
              "progress": false,
              "watch": false
            }
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "src/tsconfig.app.json",
              "src/tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        },
        "ionic-cordova-build": {
          "builder": "@ionic/angular-toolkit:cordova-build",
          "options": {
            "browserTarget": "app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "app:build:production"
            }
          }
        },
        "ionic-cordova-serve": {
          "builder": "@ionic/angular-toolkit:cordova-serve",
          "options": {
            "cordovaBuildTarget": "app:ionic-cordova-build",
            "devServerTarget": "app:serve"
          },
          "configurations": {
            "production": {
              "cordovaBuildTarget": "app:ionic-cordova-build:production",
              "devServerTarget": "app:serve:production"
            }
          }
        }
      }
    },
    "app-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "app:serve"
          },
          "configurations": {
            "ci": {
              "devServerTarget": "app:serve:ci"
            }
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": "e2e/tsconfig.e2e.json",
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "cli": {
    "schematicCollections": [
      "@ionic/angular-toolkit"
    ],
    "analytics": "4da92a43-75b3-4104-9c9e-e16caf888cb6"
  },
  "schematics": {
    "@ionic/angular-toolkit:component": {
      "styleext": "scss"
    },
    "@ionic/angular-toolkit:page": {
      "styleext": "scss"
    }
  }
}

Under “build”: {}, there does not seem to be
configurations → production

I got it working, I had comment out
// “extractCss”: true,

And then ran ionic build --configuration=production

1 Like