Weebly's site archive (Settings → General → Archive) gives you a ZIP of HTML. It doesn't give you a site that works offline. This post walks through a real archive we exported in October 2026 from our own four-page test site, and the code we use to turn it into plain static files.
Code excerpts are from our archive-to-static source (lib/weebly/ and lib/zip-io.ts).
The tree
This is unzip -l on our test export, trimmed. The ZIP has 46 entries, all under one ID-named folder:
17027946326ac7e6d662b26/
├── index.html
├── about.html
├── gallery.html
├── contact.html
├── apps/
│ └── audioPlayer2.swf
├── files/
│ ├── main_style.css
│ └── theme/
│ └── files/
│ ├── custom.js
│ ├── plugins.js
│ ├── manifest.json
│ └── images/
│ ├── default-bg.jpg
│ ├── search-icon.png
│ └── select-dropdown.png
└── uploads/1/5/6/4/156416411/
├── sourdough-loaf.jpg
├── sourdough-loaf_orig.jpg
├── berry-tart.jpg
├── berry-tart_orig.jpg
├── 1_1_orig.png … 9_1_orig.png
└── background-images/background-images/1284120290.png
What stands out:
- Pages sit flat at the root of the ID folder:
index.html,about.html, and so on. - Uploaded images come in pairs:
name.jpgplusname_orig.jpg. - Theme JS lives at
files/theme/files/, but the pages loadfiles/theme/custom.jsandfiles/theme/plugins.js. -
main_style.csssaysurl(theme/images/default-bg.jpg?1791485654). The file is actually atfiles/theme/files/images/default-bg.jpg. - Pages also load
files/templateArtifacts.js, and that file isn't in the ZIP. - A Flash file,
apps/audioPlayer2.swf, ships in the archive.
What the HTML still depends on
The <head> of index.html still loads Weebly's CDN, over plain http:
<link id="wsite-base-style" rel="stylesheet" type="text/css" href="http://cdn11.editmysite.com/css/sites.css?buildtime=1234" />
<link href='http://cdn2.editmysite.com/fonts/Open_Sans/font.css?2' rel='stylesheet' type='text/css' />
<script src="http://cdn11.editmysite.com/js/site/main.js?buildtime=1234"></script>
<script src='https://cdn11.editmysite.com/js/jquery-1.8.3.min.js'></script>
Inline scripts set up Weebly's runtime: _W = _W || {}, var IS_ARCHIVE = 1;, initPublishedFlyoutMenus(...), var STATIC_BASE = '//cdn1.editmysite.com/'.
The contact form posts to Weebly:
<form enctype="multipart/form-data" action="///weebly/apps/formSubmit.php" method="POST" id="form-369902798215628974">
Once you remove the runtime and host the files somewhere else, these are the parts our code has to deal with:
-
Styles and fonts are on
cdn*.editmysite.com. They're not in the ZIP. -
The mobile menu. The theme hides
.mobile-navand only shows it underbody.nav-open, a class that theme JS adds when you tap the hamburger. -
Forms post to
formSubmit.php, which a static host doesn't have. -
Slideshows render from inline
wSlideshow.render(...)calls. - Flash doesn't run in modern browsers.
- Blog and store pages. Our code reports: "The site-archive export does not include blog posts or store pages."
Fix 1: unwrap the ID folder
lib/zip-io.ts strips a single top-level folder when Weebly's files/, uploads/ or apps/ sits right under it:
const surfacesWeebly = [...innerTops].some((top) => /^(files|uploads|apps)$/i.test(top));
The output ZIP then has index.html at the root.
Fix 2: strip runtime scripts
lib/weebly/transform-html.ts drops every external script, every empty inline script, and every inline script that matches Weebly runtime markers:
const STRIP_INLINE =
/_W\.|IS_ARCHIVE|wSlideshow|initPublishedFlyoutMenus|formSubmit|STATIC_BASE|ASSETS_BASE|_gaq|gtag\(|pageTracker|relinquish|weebly/i;
function shouldStripScript(el: Element): boolean {
const src = el.getAttribute("src") || "";
if (src.includes("assets/ats/")) return false;
if (src && STRIP_SRC.test(src)) return true;
if (src) return true;
const text = el.textContent || "";
if (!text.trim()) return true;
return STRIP_INLINE.test(text);
}
On our test export, the rescue report listed main.js, jquery-1.8.3.min.js, stl.js, the theme's custom.js and plugins.js, templateArtifacts.js, and the _W / IS_ARCHIVE / STATIC_BASE inline blocks as stripped from all four pages. process-archive.ts also deletes the theme JS files from the output. themeJsPathsFromSrc() maps the files/theme/custom.js src to the real files/theme/files/custom.js path.
Fix 3: download remote CSS and fonts
lib/weebly/vendor.ts seeds from stylesheets on Weebly's CDN and Google Fonts:
export function isVendorStylesheet(url: string): boolean {
const u = new URL(toHttps(url));
if (/fonts\.googleapis\.com$/i.test(u.hostname)) return true;
if (/(^|\.)editmysite\.com$/i.test(u.hostname) && /\.css$/i.test(u.pathname)) return true;
return false;
}
fetchVendorAssets() downloads each one, parses url() and @import from the CSS, and queues anything on a vendor host or anything that looks like a font or CSS file. It saves everything under assets/vendor/<host>/<path> and then rewrites the HTML and CSS to relative paths. On our test export, the deployed tool fetched 149 remote assets and none failed. The output <head> now reads:
<link id="wsite-base-style" rel="stylesheet" type="text/css" href="assets/vendor/cdn11.editmysite.com/css/sites.css">
<link href="assets/vendor/cdn2.editmysite.com/fonts/Open_Sans/font.css" rel="stylesheet" type="text/css">
Any http:// or protocol-relative // URL that's left gets upgraded to https:// (toHttps, upgradeHttpInText in paths.ts).
Fix 4: theme image paths
This handles the theme/images vs theme/files/images mismatch:
/** Weebly often ships theme images at files/theme/files/images/ while CSS says files/theme/images/. */
export function themeImageFallbackPath(path: string): string | null {
const cleaned = path.replace(/\\/g, "/").split("?")[0]!.split("#")[0]!;
if (/^files\/theme\/images\//i.test(cleaned)) {
return cleaned.replace(/^files\/theme\/images\//i, "files/theme/files/images/");
}
return null;
}
stripLocalCacheBusters() removes the ?1791485654 query. In our output, main_style.css reads url(theme/files/images/default-bg.jpg).
Fix 5: prefer _orig images
/** Prefer `name_orig.ext` when that file is in the ZIP. */
export function preferOrig(path: string, files: Set<string>): string {
// ...
const orig = `${dir}${stem}_orig${ext}`;
if (files.has(orig)) return orig;
return files.has(q) ? q : path;
}
rewriteDomUrls() applies this to every <img src>. Gallery images in our test export went from sourdough-loaf.jpg to sourdough-loaf_orig.jpg.
Fix 6: the mobile menu
When the page has the theme's own hamburger, we inject a small first-party assets/ats/nav.js with no network calls and no eval. It toggles our class and the theme's class:
function setOpen(open) {
if (document.body) {
document.body.classList.toggle(OPEN, open); // "ats-nav-open"
document.body.classList.toggle(THEME_OPEN, open); // "nav-open"
}
// ...
}
Toggling the theme's nav-open class is required. In an earlier test run, our version set display and height but not opacity. The theme's .mobile-nav { opacity: 0 } stayed in force, so the menu "opened" invisibly. runtime.css now also forces opacity: 1 !important on the open state.
Pages without a theme hamburger get a CSS-only fallback: a hidden checkbox and a <label> toggle, driven by body:has(#ats-nav-toggle:checked) rules in runtime-assets.ts.
Fix 7: forms, slideshows, Flash, badges
A form gets replaced when its action matches weebly.com or formSubmit.php, or when it sits inside .wsite-form-container. The replacement is a notice: "This form cannot send messages from a static copy of the site." If you supply an email, the notice includes a mailto: link (formNoticeHtml() in gallery.ts).
Slideshows: extractSlideshowImages() pulls url: "..." values out of the wSlideshow.render script, and slideListHtml() writes them out as a plain <ul class="ats-slide-list"> of <img>. Our real export has no slideshow, so this path is covered only by our synthetic test fixture (tests/weebly/make-fixture.ts).
.swf files are deleted from the output and listed in the report: "Flash files don't run in modern browsers."
The "Create a free web site with Weebly" footer badge gets removed, with a guard. If removing it would take more than 20% of the page's text or elements (BADGE_MAX_PAGE_FRACTION = 0.2), or if the container holds #wsite-content, header, nav or main, we skip it and log a warning.
Fix 8: sitemap and report
If you enter a domain, the tool writes sitemap.xml and robots.txt. Every run also writes RESCUE-REPORT.html and _rescue-notes.txt, which list what was stripped, fetched, replaced and left out.
Doing it by hand
On a small site, the checklist is:
- Unzip and move the contents of the ID folder to the root.
- Download every
editmysite.comstylesheet. Then download the fonts and images each one references. Rewrite the URLs to relative paths. - Move or rewrite
theme/images/→theme/files/images/and drop the?123cache busters. - Point
<img>at the_origfiles. - Delete Weebly
<script>tags and the_W/IS_ARCHIVEinline blocks. - Wire the hamburger to toggle
body.nav-open. - Swap each form for a
mailto:link or a form backend of your own. - Delete
.swffiles and the footer badge.
Our free browser tool does all of the above: https://archive-to-static.pages.dev/?utm_source=devto&utm_medium=article&utm_campaign=weebly-archive-zip
Written with AI assistance from our own source code and test exports, and checked against them.
Top comments (0)