diff --git a/unpacked/MathJax.js b/unpacked/MathJax.js
index 3f9f3854b..c3fcda4bb 100644
--- a/unpacked/MathJax.js
+++ b/unpacked/MathJax.js
@@ -47,6 +47,8 @@ if (window.MathJax) {window.MathJax = {AuthorConfig: window.MathJax}}
 
 MathJax.version = "2.3";
 MathJax.fileversion = "2.3.2";
+MathJax.cdnVersion = "2.3";      // specifies a revision to break caching
+MathJax.cdnFileVersions = {}; // can be used to specify revisions for individual files
 
 /**********************************************************/
 
@@ -659,6 +661,23 @@ MathJax.fileversion = "2.3.2";
     //  Return a complete URL to a file (replacing the root pattern)
     //
     fileURL: function (file) {return file.replace(this.rootPattern,this.config.root)},
+    //
+    //  Remove root if URL includes it
+    //
+    fileName: function (url) {
+      var root = this.config.root;
+      if (url.substr(0,root.length) == root) {url = "["+BASENAME+"]"+url.substr(root.length)}
+      return url;
+    },
+    //
+    //  Cache-breaking revision number for file
+    //
+    fileRev: function (file) {
+      var rev = BASE.cdnFileVersions[name] || BASE.cdnVersion;
+      if (rev) {rev = "?rev="+rev}
+      return rev;
+    },
+    urlRev: function (file) {return this.fileURL(file)+this.fileRev(file)},
     
     //
     //  Load a file if it hasn't been already.
@@ -739,6 +758,7 @@ MathJax.fileversion = "2.3.2";
       //  Create a SCRIPT tag to load the file
       //
       JS: function (file,callback) {
+        var name = this.fileName(file);
         var script = document.createElement("script");
         var timeout = BASE.Callback(["loadTimeout",this,file]);
         this.loading[file] = {
@@ -747,23 +767,27 @@ MathJax.fileversion = "2.3.2";
           status: this.STATUS.OK,
           script: script
         };
+        //
         // Add this to the structure above after it is created to prevent recursion
         //  when loading the initial localization file (before loading messsage is available)
-        this.loading[file].message = BASE.Message.File(file);
+        //
+        this.loading[file].message = BASE.Message.File(name);
         script.onerror = timeout;  // doesn't work in IE and no apparent substitute
         script.type = "text/javascript";
-        script.src = file;
+        script.src = file+this.fileRev(name);
         this.head.appendChild(script);
       },
       //
       //  Create a LINK tag to load the style sheet
       //
       CSS: function (file,callback) {
+        var name = this.fileName(file);
         var link = document.createElement("link");
-        link.rel = "stylesheet"; link.type = "text/css"; link.href = file;
+        link.rel = "stylesheet"; link.type = "text/css";
+        link.href = file+this.fileRev(name);
         this.loading[file] = {
           callback: callback,
-          message: BASE.Message.File(file),
+          message: BASE.Message.File(name),
           status: this.STATUS.OK
         };
         this.head.appendChild(link);
@@ -1766,8 +1790,6 @@ MathJax.Message = {
   },
   
   File: function (file) {
-    var root = MathJax.Ajax.config.root;
-    if (file.substr(0,root.length) === root) {file = "[MathJax]"+file.substr(root.length)}
     return this.Set(["LoadFile","Loading %1",file],null,null);
   },
   
diff --git a/unpacked/extensions/HelpDialog.js b/unpacked/extensions/HelpDialog.js
index 4d22fa661..cc2928efa 100644
--- a/unpacked/extensions/HelpDialog.js
+++ b/unpacked/extensions/HelpDialog.js
@@ -34,7 +34,7 @@
   var MENU = MathJax.Menu;
 
   var CONFIG = HUB.CombineConfig("HelpDialog",{
-    closeImg: AJAX.fileURL(OUTPUT.imageDir+"/CloseX-31.png"), // image for close "X" for mobiles
+    closeImg: AJAX.urlRev(OUTPUT.imageDir+"/CloseX-31.png"), // image for close "X" for mobiles
 
     styles: {
       "#MathJax_Help": {
diff --git a/unpacked/extensions/MathEvents.js b/unpacked/extensions/MathEvents.js
index 92b2745f9..ff3c6ae98 100644
--- a/unpacked/extensions/MathEvents.js
+++ b/unpacked/extensions/MathEvents.js
@@ -45,7 +45,7 @@
     button: {
       x: -4, y: -3,          // menu button offsets
       wx: -2,                // button offset for full-width equations
-      src: AJAX.fileURL(OUTPUT.imageDir+"/MenuArrow-15.png")  // button image
+      src: AJAX.urlRev(OUTPUT.imageDir+"/MenuArrow-15.png")  // button image
     },
     fadeinInc: .2,           // increment for fade-in
     fadeoutInc: .05,         // increment for fade-out
diff --git a/unpacked/extensions/MathMenu.js b/unpacked/extensions/MathMenu.js
index 4e91dff1e..5c948f585 100644
--- a/unpacked/extensions/MathMenu.js
+++ b/unpacked/extensions/MathMenu.js
@@ -48,7 +48,7 @@
   
   var CONFIG = HUB.CombineConfig("MathMenu",{
     delay: 150,                                    // the delay for submenus
-    closeImg: AJAX.fileURL(OUTPUT.imageDir+"/CloseX-31.png"), // image for close "X" for mobiles
+    closeImg: AJAX.urlRev(OUTPUT.imageDir+"/CloseX-31.png"), // image for close "X" for mobiles
 
     showRenderer: true,                            //  show the "Math Renderer" menu?
     showMathPlayer: true,                          //  show the "MathPlayer" menu?
diff --git a/unpacked/jax/output/HTML-CSS/imageFonts.js b/unpacked/jax/output/HTML-CSS/imageFonts.js
index 240db3c08..0cbdae548 100644
--- a/unpacked/jax/output/HTML-CSS/imageFonts.js
+++ b/unpacked/jax/output/HTML-CSS/imageFonts.js
@@ -61,10 +61,10 @@
         if (c[4] != c[2]) {style.marginRight = this.Em((c[2]-c[4])/1000)}
         if (this.msieIE6) {
           style.filter = "progid:DXImageTransform.Microsoft." +
-            "AlphaImageLoader(src='"+AJAX.fileURL(file)+"', sizingMethod='scale')";
+            "AlphaImageLoader(src='"+AJAX.urlRev(file)+"', sizingMethod='scale')";
           file = this.directory+"/blank.gif"
         }
-        this.addElement(span,"img",{src:AJAX.fileURL(file), style:style, isMathJax:true});
+        this.addElement(span,"img",{src:AJAX.urlRev(file), style:style, isMathJax:true});
         return "";
       },
       
diff --git a/unpacked/jax/output/HTML-CSS/jax.js b/unpacked/jax/output/HTML-CSS/jax.js
index 5cd53cbaa..b52fbb4b5 100644
--- a/unpacked/jax/output/HTML-CSS/jax.js
+++ b/unpacked/jax/output/HTML-CSS/jax.js
@@ -214,18 +214,22 @@
       var type = HTMLCSS.allowWebFonts;
       var FONT = HTMLCSS.FONTDATA.FONTS[name];
       if (HTMLCSS.msieFontCSSBug && !FONT.family.match(/-Web$/)) {FONT.family += "-Web"}
-      var dir = AJAX.fileURL(HTMLCSS.webfontDir+"/"+type);
+      var webfonts = HTMLCSS.webfontDir+"/"+type;
+      var dir = AJAX.fileURL(webfonts);
       var fullname = name.replace(/-b/,"-B").replace(/-i/,"-I").replace(/-Bold-/,"-Bold");
       if (!fullname.match(/-/)) {fullname += "-Regular"}
       if (type === "svg") {fullname += ".svg#"+fullname} else {fullname += "."+type}
+      var rev = AJAX.fileRev(webfonts+"/"+fullname.replace(/#.*/,""));
       var def = {
         "font-family": FONT.family,
-        src: "url('"+dir+"/"+fullname+"')"
+        src: "url('"+dir+"/"+fullname+rev+"')"
       };
       if (type === "otf") {
+        fullname = fullname.replace(/otf$/,"woff");
+        rev = AJAX.fileRev(webfonts+"/"+fullname);
         def.src += " format('opentype')";
         dir = AJAX.fileURL(HTMLCSS.webfontDir+"/woff");  // add woff fonts as well
-        def.src = "url('"+dir+"/"+fullname.replace(/otf$/,"woff")+"') format('woff'), "+def.src;
+        def.src = "url('"+dir+"/"+fullname+rev+"') format('woff'), "+def.src;
       } else if (type !== "eot") {def.src += " format('"+type+"')"}
       if (!(HTMLCSS.FontFaceBug && FONT.isWebFont)) {
         if (name.match(/-bold/)) {def["font-weight"] = "bold"}