DEV Community

labyrinthlab
labyrinthlab

Posted on

What's actually inside a Weebly archive ZIP

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
Enter fullscreen mode Exit fullscreen mode

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.jpg plus name_orig.jpg.
  • Theme JS lives at files/theme/files/, but the pages load files/theme/custom.js and files/theme/plugins.js.
  • main_style.css says url(theme/images/default-bg.jpg?1791485654). The file is actually at files/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>
Enter fullscreen mode Exit fullscreen mode

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">
Enter fullscreen mode Exit fullscreen mode

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-nav and only shows it under body.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));
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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">
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Unzip and move the contents of the ID folder to the root.
  2. Download every editmysite.com stylesheet. Then download the fonts and images each one references. Rewrite the URLs to relative paths.
  3. Move or rewrite theme/images/ → theme/files/images/ and drop the ?123 cache busters.
  4. Point <img> at the _orig files.
  5. Delete Weebly <script> tags and the _W / IS_ARCHIVE inline blocks.
  6. Wire the hamburger to toggle body.nav-open.
  7. Swap each form for a mailto: link or a form backend of your own.
  8. Delete .swf files 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)