Instaloader metadata was silently lost Every .json.xz failed with "Failed to fetch" during a scan, though the same URL fetched fine on its own. RemoteArchiveFile.stream() started a fetch, piped the body into a TransformStream and returned the readable immediately — nothing caught a fetch rejection, and the decompressor stops reading at the end of the xz member, so the response body was never drained or cancelled. Across ~190 sidecars that exhausted the connection pool. Everything Instaloader archives carry lives in those files, so the failure was invisible but total. rivvsofficial reported 188 posts, no stories, 0 followers and a placeholder bio; it now reports 68 posts, 120 stories, 10,337 followers and the real name, bio and link — 68 + 120 = 188, matching the sidecars exactly (106 GraphStoryVideo + 14 GraphStoryImage = 120). These sidecars are a few KB, so they are now read into memory before decompressing. stream() was left unused by that change and is removed from the interface and both implementations rather than kept as a trap. Arrow keys page the carousel They moved between posts, which contradicted the arrows drawn on the carousel itself. Arrows now page slides; , and . move between posts, alongside the side buttons. Backdrop cross-fade AnimatePresence had no exit variant, so the outgoing scan backdrop was removed instantly while its replacement faded in over 1.5s, exposing the pale page behind it as a white flash. Layers now stack: the outgoing image holds full opacity until covered, and the 0.4 moved onto the group so overlapping layers don't darken as they cross. Measured over a real scan: 152 cross-fades with a layer always opaque, except the opening fade-in where nothing is underneath. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uBWhwV3wFQ5MBCcMHHem7
InstaArchive Viewer
A high-performance React PWA for browsing archived Instagram data with a native-feeling interface. Supports both official Instagram exports and Instaloader archives.
Features
- Advanced Carousel: Seamless, zero-latency transitions between slides with intelligent preloading. Navigating between different posts is now near-instant thanks to inter-post background preloading.
- High-Res Performance: Handles 50MP+ images effortlessly using a background Web Worker and a memory-safe serial processing queue.
- Persistent Local Caching: Uses IndexedDB to store parsed archives and generated thumbnails. Local folders now load instantly from cache on return visits without needing to re-upload files.
- Permalinks: State is synchronized with the URL, allowing you to share direct links to archives, tabs, or specific posts. Navigating back to the explorer cleans up URL parameters automatically.
- Glassy Scanning UI: A refined, translucent white terminal experience with flicker-free, double-buffered dynamic blurred backgrounds.
- PWA with Auto-Update: Fully offline-capable and installable. Clients automatically receive updates when a new version is deployed to the server.
- Local Privacy: All processing is done client-side. Even when using the self-hosted version, your media is processed locally in your browser and never uploaded.
- Smart Fallbacks: Automatically detects usernames from folder names and uses the oldest archive image as a profile picture if one is missing.
- Customizable Grid: 1:1 or 3:4 aspect ratios with adjustable "bumps" for aesthetic alignment.
- Story Viewer: Native-like story experience with segmented progress bars, auto-playback, and audio controls.
- Navigation Protection: Intercepts accidental browser "Back" or "Refresh" actions to protect your current session.
Deployment
Docker (Recommended)
The easiest way to run InstaArchive is using Docker.
docker run -d \
-p 3000:3000 \
-v /path/to/your/archives:/archives:ro \
ghcr.io/ergosteur/instaarchive-viewer:latest
Note for Linux/SELinux users: If you see "Permission Denied" in the logs, append
,zto your volume mount:-v /path/to/archives:/archives:ro,z
Docker Compose
Create a compose.yml file:
services:
instaarchive:
image: ghcr.io/ergosteur/instaarchive-viewer:latest
ports:
- "3000:3000"
volumes:
- ./archives:/archives:ro,z # ,z handles SELinux permissions
Troubleshooting Permissions
If the app shows "No Archives Found" and logs EACCES: permission denied:
- Check Directory Permissions: Ensure the archive folder is world-readable:
chmod -R 755 /path/to/archives - SELinux (Fedora/RHEL/CentOS): Use the
:zflag in your volume mount as shown above. - User Mapping: The container runs as the non-root
nodeuser (UID 1000). If your archives are readable only by another account, run as that user instead — the container needs to list the archive directory, so--x(traverse-only) permissions are not enough:In Compose:docker run --user $(stat -c '%u:%g' /path/to/archives) ...services: instaarchive: user: "1234:1234" # a UID that can read your archives
Archive Index
On first start the server walks the archive root once and caches the result,
keyed by directory mtime. This matters on network storage: for a 110k-file
archive root, listing went from ~52s per request to ~0.1s. Mount a volume at
/cache (or set CACHE_DIR) so the index survives restarts, otherwise it is
rebuilt on every start.
Supported Archive Structure
Place your archive folders inside the mounted /archives directory. The directory name will be used as the account username.
Example Structure:
archives/
├── wanderlust_explorer/ # Instaloader format
│ ├── 2024-01-01_12-00-00_UTC.jpg
│ ├── 2024-01-01_12-00-00_UTC.json.xz
│ └── wanderlust_explorer_profile_pic.jpg
└── pixel_architect/ # Instagram Export format
├── 2023-12-25_pixel_architect - post_123.jpg
├── 2023-12-25_pixel_architect - post_123.json
└── pixel_architect.jpg
Local Development
Prerequisites: Node.js (LTS recommended)
- Install dependencies:
npm install - Start dev server:
npm run dev(Frontend on port 3000) - Start local backend:
npm run server(Optional, serves./_sample-archiveson port 3001) - Build production:
npm run build(Generates./distfor frontend and./dist-serverfor the API)