Angular 2 사이트에서 브라우저 캐시를 방지하는 방법은 무엇입니까?


104

우리는 현재 고객 중 한 명이 매일 정기적으로 업데이트하는 새로운 프로젝트를 진행하고 있습니다. 이 프로젝트는 angular 2를 사용하여 개발 중이며 캐시 문제에 직면 해 있습니다. 즉, 클라이언트가 컴퓨터에서 최신 변경 사항을 보지 못하고 있습니다.

주로 js 파일의 html / css 파일은 많은 문제를 일으키지 않고 제대로 업데이트되는 것 같습니다.


2
아주 좋은 질문입니다. 나도 같은 문제가있어. 이 문제를 해결하는 가장 좋은 방법은 무엇입니까? Gulp 또는 Angular 2 응용 프로그램을 게시하기위한 유사한 도구로 가능합니까?
jump4791

2
@ jump4791 가장 좋은 방법은 웹팩을 사용하고 프로덕션 설정을 사용하여 프로젝트를 컴파일하는 것입니다. 저는 현재이 저장소를 사용하고 있습니다. 단계를 따르 시면
Rikku121

나도 같은 문제가 있습니다.
Ziggler

3
나는 이것이 오래된 질문이라는 것을 알고 있지만 이것에 대해 일어나는 모든 사람들을 위해 내가 찾은 해결책을 추가하고 싶었습니다. 건물 때 ng build의 추가 -prod태그하면 생성 된 파일 이름에 해시를 추가합니다. 이렇게하면 index.html. 이 github 게시물 에는 다시로드하는 방법에 대한 몇 가지 힌트가 있습니다.
Tiz

2
index.html이 근본 원인입니다. 해시 코드가 없기 때문에 캐시되면 다른 모든 것이 캐시에서 사용됩니다.
Fiona

답변:


179

angular-cli--output-hashing빌드 명령에 플래그를 제공하여이 문제를 해결합니다 (버전 6/7, 이후 버전의 경우 여기 참조 ). 사용 예 :

ng build --output-hashing=all

Bundling & Tree-Shaking 은 몇 가지 세부 사항과 컨텍스트를 제공합니다. 를 실행 ng help build하면 플래그가 문서화됩니다.

--output-hashing=none|all|media|bundles (String)

Define the output filename cache-busting hashing mode.
aliases: -oh <value>, --outputHashing <value>

angular-cli 사용자에게만 적용 할 수 있지만 훌륭하게 작동하며 코드 변경이나 추가 도구가 필요하지 않습니다.

최신 정보

댓글의 수는 한 유용하게 그리고 정확하게 이 대답은에 해시를 추가하는 지적 .js하지만 파일에 대한 아무것도하지 않습니다 index.html. 따라서 캐시가 파일을 파열 index.html한 후에도 ng build캐시 된 상태로 남아 있을 수 .js있습니다.

이 시점 에서 모든 브라우저에서 웹 페이지 캐싱어떻게 제어합니까?


14
이 작업을 수행하는 적절한 방법이며 선택한 답변이어야합니다!
jonesy827 dec.

1
이것은 우리 앱에서 작동하지 않았습니다. 쿼리 문자열 매개 변수가있는 templateUrl이 너무 나빠 CLI에서 작동하지 않습니다
DDiVita

8
index.html이 브라우저에 의해 캐시 된 경우 작동하지 않으므로 자바 스크립트 리소스에 대한 새 해시 이름이 표시되지 않습니다. 나는 이것이 이것과 @Rossco가 준 대답의 조합이 의미가 있다고 생각합니다. 또한 전송 된 HTTP 헤더와 일관성을 유지하는 것이 좋습니다.
stryba

2
@stryba 이것이 html 캐싱이 다르게 처리되어야하는 이유입니다. 캐싱이 발생하지 않도록 Cache-Control, Pragma 및 Expires 응답 헤더를 지정해야합니다. 백엔드 프레임 워크를 사용하는 경우에는 쉽지만 Apache 용 .htaccess 파일에서도 처리 할 수 ​​있다고 생각합니다 (하지만 nginx에서 작동하는 방법은 idk).
OzzyTheGiant

3
이 답변은 js 파일에 해시를 추가합니다. 그러나 stryba가 말했듯이 index.html이 캐시되지 않았는지 확인해야합니다. html 메타 태그를 사용하지 말고 응답 헤더 cache-control : no-cache (또는 더 멋진 캐싱 전략을위한 다른 헤더)를 사용하여이 작업을 수행해서는 안됩니다.
Noppey

34

이 작업을 수행하는 방법을 찾았다면 다음과 같이 구성 요소를로드 할 쿼리 문자열을 추가하기 만하면됩니다.

@Component({
  selector: 'some-component',
  templateUrl: `./app/component/stuff/component.html?v=${new Date().getTime()}`,
  styleUrls: [`./app/component/stuff/component.css?v=${new Date().getTime()}`]
})

이렇게하면 클라이언트가 브라우저 대신 서버의 템플릿 사본을로드해야합니다. 일정 시간이 지난 후에 만 ​​새로 고치려면이 ISOString을 대신 사용할 수 있습니다.

new Date().toISOString() //2016-09-24T00:43:21.584Z

예를 들어 한 시간 후에 만 ​​변경되도록 일부 문자를 하위 문자열로 만듭니다.

new Date().toISOString().substr(0,13) //2016-09-24T00

도움이 되었기를 바랍니다


3
그래서 내 구현은 실제로 작동하지 않았습니다. 캐싱은 이상한 문제입니다. 때로는 작동하고 때로는 작동하지 않습니다. 오 간헐적 인 문제의 아름다움. 실제로 같은에 대한 답을 적응 그래서 :templateUrl: './app/shared/menu/menu.html?v=' + Math.random()
Rossco

내 templateUrls에 404가 표시됩니다. 예 : GET localhost : 8080 / app.component.html /? v = 0.0.1-alpha 404 (찾을 수 없음) 이유를 아십니까?
Shenbo

@ Rikku121 아니 그렇지 않습니다. 실제로 URL에 /가 없습니다. 내가 댓글을 게시 할 때 실수로 추가했을 수도 있습니다
Shenbo

15
코드 변경이없는 경우에도 매번 캐시를 버스 팅 할 때 캐싱의 요점은 무엇입니까?
Apurv Kamalapuri

1
ng build --aot --build-optimizer = true --base-href = / <url> / 오류 발생 --- 리소스를 확인할 수 없습니다 ./login.component.html?v=${new Date (). 다음 getTime ()}
Pranjal Successena

23

각 HTML 템플릿에서 맨 위에 다음 메타 태그를 추가합니다.

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">

내 이해에서 각 템플릿은 독립형이므로 index.html 파일에 설정된 캐싱 규칙이없는 메타를 상속하지 않습니다.


4
우리는 당분간 webpack으로 전환했으며 각 앱의 캐시 버스 팅을 처리합니다. 그래도 솔루션이 작동한다는 것을 아는 것이 좋습니다. 감사합니다
Rikku121

너무 나를 위해 한
iniravpatel

4

@Jack의 대답과 @ranierbit의 대답의 조합이 트릭을 수행해야합니다.

--output-hashing에 대한 ng 빌드 플래그를 설정합니다.

ng build --output-hashing=all

그런 다음 서비스 또는 앱에서이 클래스를 추가합니다.

@Injectable()
export class NoCacheHeadersInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const authReq = req.clone({
            setHeaders: {
                'Cache-Control': 'no-cache',
                 Pragma: 'no-cache'
            }
        });
        return next.handle(authReq);    
    }
}

그런 다음 app.module의 공급자에 추가하십시오.

providers: [
  ... // other providers
  {
    provide: HTTP_INTERCEPTORS,
    useClass: NoCacheHeadersInterceptor,
    multi: true
  },
  ... // other providers
]

이렇게하면 클라이언트 컴퓨터의 라이브 사이트에서 캐싱 문제가 방지됩니다.


3

index.html이 브라우저에 의해 캐시되거나 중간 cdn / 프록시에 의해 더 까다로워지는 것과 비슷한 문제가있었습니다 (F5는 도움이되지 않습니다).

클라이언트가 최신 index.html 버전을 가지고 있는지 100 % 확인하는 솔루션을 찾았습니다. 다행히 Henrik Peinar가이 솔루션을 찾았습니다.

https://blog.nodeswat.com/automagic-reload-for-clients-after-deploy-with-angular-4-8440c9fdd96c

이 솔루션은 클라이언트가 브라우저를 며칠 동안 열어 둔 채로있는 경우도 해결하고 클라이언트는 간격에 따라 업데이트를 확인하고 새 버전이 배포 된 경우 다시로드합니다.

솔루션은 약간 까다 롭지 만 매력적으로 작동합니다.

  • ng cli -- prodmain. [hash] .js 중 하나와 함께 해시 파일 을 생성 한다는 사실을 사용하십시오 .
  • 해당 해시를 포함하는 version.json 파일을 만듭니다.
  • version.json을 확인하고 필요한 경우 다시로드하는 각도 서비스 VersionCheckService를 만듭니다.
  • 배포 후 실행되는 js 스크립트는 version.json을 생성하고 각도 서비스의 해시를 대체하므로 수동 작업이 필요하지 않지만 post-build.js를 실행합니다.

Henrik Peinar 솔루션은 각도 4 용이므로 약간의 변경이 있었으므로 여기에 고정 스크립트도 배치합니다.

VersionCheckService :

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class VersionCheckService {
    // this will be replaced by actual hash post-build.js
    private currentHash = '{{POST_BUILD_ENTERS_HASH_HERE}}';

    constructor(private http: HttpClient) {}

    /**
     * Checks in every set frequency the version of frontend application
     * @param url
     * @param {number} frequency - in milliseconds, defaults to 30 minutes
     */
    public initVersionCheck(url, frequency = 1000 * 60 * 30) {
        //check for first time
        this.checkVersion(url); 

        setInterval(() => {
            this.checkVersion(url);
        }, frequency);
    }

    /**
     * Will do the call and check if the hash has changed or not
     * @param url
     */
    private checkVersion(url) {
        // timestamp these requests to invalidate caches
        this.http.get(url + '?t=' + new Date().getTime())
            .subscribe(
                (response: any) => {
                    const hash = response.hash;
                    const hashChanged = this.hasHashChanged(this.currentHash, hash);

                    // If new version, do something
                    if (hashChanged) {
                        // ENTER YOUR CODE TO DO SOMETHING UPON VERSION CHANGE
                        // for an example: location.reload();
                        // or to ensure cdn miss: window.location.replace(window.location.href + '?rand=' + Math.random());
                    }
                    // store the new hash so we wouldn't trigger versionChange again
                    // only necessary in case you did not force refresh
                    this.currentHash = hash;
                },
                (err) => {
                    console.error(err, 'Could not get version');
                }
            );
    }

    /**
     * Checks if hash has changed.
     * This file has the JS hash, if it is a different one than in the version.json
     * we are dealing with version change
     * @param currentHash
     * @param newHash
     * @returns {boolean}
     */
    private hasHashChanged(currentHash, newHash) {
        if (!currentHash || currentHash === '{{POST_BUILD_ENTERS_HASH_HERE}}') {
            return false;
        }

        return currentHash !== newHash;
    }
}

주요 AppComponent로 변경합니다.

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    constructor(private versionCheckService: VersionCheckService) {

    }

    ngOnInit() {
        console.log('AppComponent.ngOnInit() environment.versionCheckUrl=' + environment.versionCheckUrl);
        if (environment.versionCheckUrl) {
            this.versionCheckService.initVersionCheck(environment.versionCheckUrl);
        }
    }

}

마법을 만드는 포스트 빌드 스크립트, post-build.js :

const path = require('path');
const fs = require('fs');
const util = require('util');

// get application version from package.json
const appVersion = require('../package.json').version;

// promisify core API's
const readDir = util.promisify(fs.readdir);
const writeFile = util.promisify(fs.writeFile);
const readFile = util.promisify(fs.readFile);

console.log('\nRunning post-build tasks');

// our version.json will be in the dist folder
const versionFilePath = path.join(__dirname + '/../dist/version.json');

let mainHash = '';
let mainBundleFile = '';

// RegExp to find main.bundle.js, even if it doesn't include a hash in it's name (dev build)
let mainBundleRegexp = /^main.?([a-z0-9]*)?.js$/;

// read the dist folder files and find the one we're looking for
readDir(path.join(__dirname, '../dist/'))
  .then(files => {
    mainBundleFile = files.find(f => mainBundleRegexp.test(f));

    if (mainBundleFile) {
      let matchHash = mainBundleFile.match(mainBundleRegexp);

      // if it has a hash in it's name, mark it down
      if (matchHash.length > 1 && !!matchHash[1]) {
        mainHash = matchHash[1];
      }
    }

    console.log(`Writing version and hash to ${versionFilePath}`);

    // write current version and hash into the version.json file
    const src = `{"version": "${appVersion}", "hash": "${mainHash}"}`;
    return writeFile(versionFilePath, src);
  }).then(() => {
    // main bundle file not found, dev build?
    if (!mainBundleFile) {
      return;
    }

    console.log(`Replacing hash in the ${mainBundleFile}`);

    // replace hash placeholder in our main.js file so the code knows it's current hash
    const mainFilepath = path.join(__dirname, '../dist/', mainBundleFile);
    return readFile(mainFilepath, 'utf8')
      .then(mainFileData => {
        const replacedFile = mainFileData.replace('{{POST_BUILD_ENTERS_HASH_HERE}}', mainHash);
        return writeFile(mainFilepath, replacedFile);
      });
  }).catch(err => {
    console.log('Error with post build:', err);
  });

(새) 빌드 폴더에 스크립트를 node ./build/post-build.js넣으십시오.ng build --prod


1

HTTP 헤더로 클라이언트 캐시를 제어 할 수 있습니다. 이것은 모든 웹 프레임 워크에서 작동합니다.

이러한 헤더의 지시문을 설정하여 캐시를 활성화하는 방법과시기를 세밀하게 제어 할 수 있습니다.

  • Cache-Control
  • Surrogate-Control
  • Expires
  • ETag (아주 좋은 것)
  • Pragma (이전 브라우저를 지원하려는 경우)

좋은 캐싱은 모든 컴퓨터 시스템에서 좋지만 매우 복잡합니다 . 자세한 내용 은 https://helmetjs.github.io/docs/nocache/#the-headers 를 참조하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.