October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
Firefox 59

How to Follow URL Redirects and Screenshot the Final Page with SlimerJS

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Call page.open(), wait for its asynchronous completion callback (or promise), read page.url, and only then call page.render(). That sequence follows normal browser redirects and captures the resulting document. SlimerJS is an archival tool, however: its project website says development stopped in 2018 and that it works only with Firefox 59; newer Firefox releases are unsupported.

Important: SlimerJS is a legacy Firefox 59 environment

SlimerJS is a JavaScript-driven browser built on Gecko. The official project site lists SlimerJS 1.0.0, supports Firefox 59, and says higher Firefox versions will not be supported because development ceased in 2018. Treat the procedure below as maintenance guidance for an existing legacy setup, not as a recommendation for new production automation. Reproduce the exact SlimerJS and Firefox versions used by your script before relying on a result.

The examples use the documented webpage API. They are illustrative patterns rather than a claim that the code was run against a live site.

The redirect-and-capture sequence

  1. Open the starting URL. page.open(startUrl, callback) starts navigation and returns before the page is ready.
  2. Wait for the callback or promise continuation. Any URL read or render issued immediately after page.open() can run before navigation finishes.
  3. Record page.url. After the main document has loaded, this is the URL currently shown by the page object—the final URL after ordinary HTTP redirects and any subsequent browser navigation.
  4. Optionally collect redirect metadata. In onResourceReceived, a response can expose redirectURL. Store those pairs when you need a diagnostic chain, but do not assume every redirect will provide that optional field.
  5. Set the viewport, then wait for reflow. Assigning viewportSize triggers an asynchronous layout update.
  6. Render after the update. Call page.render() only after the viewport has been applied and the page-specific visual state is ready.

Complete callback-based example

Callback style is the most compatible shape for older PhantomJS-style code. This script follows a short link, records the resulting URL and any observed redirect targets, sets a 1,280 × 900 viewport, and saves a viewport-only PNG.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = require('webpage').create();
var startUrl = 'https://example.com/short-link';
var redirectTargets = [];

page.onResourceReceived = function (response) {
    if (response.redirectURL) {
        redirectTargets.push({ from: response.url, to: response.redirectURL });
    }
};

page.open(startUrl, function (status) {
    if (status !== 'success') {
        console.log('Navigation failed: ' + status);
        page.close();
        slimer.exit();
        return;
    }

    console.log('Current page URL: ' + page.url);
    console.log('Redirect response metadata: ' + JSON.stringify(redirectTargets));

    page.viewportSize = { width: 1280, height: 900 };
    // viewportSize triggers asynchronous reflow; wait before capture.
    window.setTimeout(function () {
        page.render('final-page.png', { onlyViewport: true });
        page.close();
        slimer.exit();
    }, 500);
});

Replace https://example.com/short-link with the URL you need to inspect. The 500-millisecond delay is only a simple compatibility-friendly pause. It is not a guarantee that every framework, animation, lazy image, or client-side redirect has settled.

Getting the final URL reliably

Use page.url for the browser’s resulting location

Read page.url inside the completion callback, after page.open() reports success. This answers “How can I get the final URL after a redirect in SlimerJS?” for the page the browser ended up displaying. It also reflects later script-driven navigation, not just an HTTP 3xx hop.

Use redirectURL for redirect diagnostics

onResourceReceived receives response records while resources load. When a record contains redirectURL, save response.url as the source and response.redirectURL as the target. This can reveal intermediate HTTP redirect steps that are no longer visible in page.url. The property is optional metadata, so an empty list does not prove that no redirect occurred.

Keep status codes if HTTP policy matters

Store response status values from resource events when your job must distinguish a successful document from an error page. SlimerJS documentation notes that the load status can be success for a valid HTTP response even when that response is a 404. Decide explicitly whether your workflow should capture such pages for debugging or reject them for a success-only report.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use onLoadFinished without capturing a subframe

An event-driven script can use onLoadFinished(status, url, isFrame) instead of the page.open() callback. The event fires for the top-level document and for frames. If the action belongs only to the final page, require isFrame === false before reading the URL or scheduling a render.

var page = require('webpage').create();
var captured = false;

page.onLoadFinished = function (status, url, isFrame) {
    if (isFrame || captured) {
        return;
    }
    captured = true;

    if (status !== 'success') {
        console.log('Main-document load failed: ' + status);
        page.close();
        slimer.exit();
        return;
    }

    console.log('Final top-level URL: ' + page.url);
    page.viewportSize = { width: 1280, height: 900 };
    window.setTimeout(function () {
        page.render('final-page.png', { onlyViewport: true });
        page.close();
        slimer.exit();
    }, 500);
};

page.open('https://example.com/short-link');

The captured guard prevents a later frame event from starting a second capture. If your page intentionally navigates the top-level document more than once, replace that guard with a condition that identifies the navigation you actually want.

Callback style versus promise style

SlimerJS also documents a promise returned by navigation methods. Promise chaining can read more naturally when several asynchronous operations must happen in sequence, but the quick-start documentation notes that this promise style is not compatible with PhantomJS. Use the callback form when sharing code with an older PhantomJS-compatible codebase.

var page = require('webpage').create();

page.open('https://example.com/short-link')
    .then(function (status) {
        if (status !== 'success') {
            throw new Error('Navigation failed: ' + status);
        }
        console.log('Final URL: ' + page.url);
        page.viewportSize = { width: 1280, height: 900 };
        return new Promise(function (resolve) {
            window.setTimeout(resolve, 500);
        });
    })
    .then(function () {
        page.render('final-page.png', { onlyViewport: true });
        page.close();
        slimer.exit();
    })
    .catch(function (error) {
        console.log(error.message);
        page.close();
        slimer.exit();
    });

Use the promise form only in the SlimerJS version that provides it, and verify the exact promise behavior in the installed 1.0.0 documentation. The callback example above is the safer baseline for legacy scripts.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing the screenshot area and format

Viewport-only output

page.render('file.png', { onlyViewport: true }) captures what fits in the configured viewport. This is appropriate for responsive-layout checks, visual regression at a known screen size, or a screenshot that represents what a visitor initially sees.

Full content or a clipped region

Without onlyViewport, the capture can encompass page content rather than only the visible viewport. Use clipRect when you need a defined rectangle. Decide the scope before comparing images: a full document and a 900-pixel viewport answer different questions.

Supported output types

The API lists PNG, JPEG/JPG, PDF, BMP, and ICO formats. The quick start uses PNG. Gecko does not provide GIF output. Match the filename extension and render settings to the artifact your downstream process expects.

Waiting for a page that keeps changing

Load completion means the browser received a valid main-document response; it does not mean that a single-page application has finished fetching data or that lazy images have appeared. A fixed timeout is easy to understand but can be too short on a slow page and wasteful on a fast one.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Prefer a page-specific readiness test in your own script, such as checking for a known DOM state from code evaluated in the page.
  • Use a bounded fallback delay so a broken page cannot keep the process alive indefinitely.
  • Set the viewport before the final readiness check when layout-dependent elements are involved.
  • Capture only after client-side navigation, fonts, images, and animations relevant to your use case have settled.

SlimerJS API documentation also mentions slimer.wait(500) for allowing asynchronous viewport or zoom changes to apply. That helper is not compatible with PhantomJS, so use it only when your runtime is definitely SlimerJS; otherwise a timer inside the page script is the more portable illustration.

Troubleshooting common failures

The screenshot shows the original short-link page

Cause: rendering happened immediately after page.open(), before its asynchronous navigation completed. Fix: move URL inspection, viewport assignment, and page.render() into the callback or promise continuation. If the site performs a second client-side navigation, add a readiness condition for that transition.

The URL is final, but the image has the old layout

Cause: assigning viewportSize schedules an asynchronous reflow. Fix: wait after setting it, then render. A 500-millisecond example is only a starting point; increase or replace it with a page-specific condition when the layout is still changing.

A frame triggers an unwanted capture

Cause: onLoadFinished fires for frames as well as the main document. Fix: test isFrame === false and guard against duplicate top-level captures.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The callback says success for a 404

Cause: SlimerJS treats receipt of a valid HTTP response as a successful load, including a 404. Fix: collect response status codes and apply your own policy: keep the screenshot for error diagnostics or stop before rendering when only 2xx pages are acceptable.

No redirect target appears in the log

Cause: redirectURL is optional response metadata and may not be populated for every navigation path. Fix: use page.url as the authoritative resulting browser URL, and treat the response records as supplementary diagnostics.

The script works on one machine but not another

Cause: SlimerJS depends on an obsolete Firefox 59-compatible environment. Fix: pin and document the exact SlimerJS and Firefox versions, then validate the page behavior in that isolated environment. Do not assume a current Firefox installation is interchangeable.

The output is unexpectedly huge or cropped

Cause: capture scope differs from your intent. Fix: use onlyViewport: true for the configured viewport, or set an explicit clipRect for a region. For content captures, check the page’s calculated dimensions before rendering.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Operational guidance for a legacy capture job

  • Log both URLs: the requested start URL and page.url after load.
  • Log redirect records: retain each from/to pair and response status when available.
  • Make the error policy explicit: a 404 can be a useful artifact or a failed job, depending on your purpose.
  • Close every page: call page.close() on success and failure, then exit the SlimerJS process.
  • Keep timing bounded: combine a readiness check with a maximum wait so a stalled script cannot run forever.
  • Record capture parameters: viewport dimensions, output format, onlyViewport, and any clip rectangle belong in the job log.

SlimerJS itself is free and open source, but its discontinued browser dependency is the principal reliability risk. The documentation and CasperJS materials are legacy references; behavior should be checked against the exact installed runtime when a production result matters. CasperJS users may also encounter a documented waitForUrl() helper, but that does not replace understanding SlimerJS’s own page.url, load events, and render timing.

Or skip the browser setup

If you do not need to preserve a Firefox 59 environment, ScreenshotNeo provides a current website screenshot API and MCP server. It follows redirects for you and returns a PNG, JPEG, WebP, or PDF from one request.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request options. The same request from Python is:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Other controls include full-page capture with lazy images, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS-to-image, custom JavaScript and CSS, clicks before capture, selector hiding, selector/delay/network-idle waits, request and resource blocking, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed public-image links, asynchronous jobs with signed webhooks, bulk capture for 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs.

Plan Included screenshots Price
Free 1,000 per month $0, no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Every feature is available on every plan, and yearly billing gives two months free. You can start with 1,000 screenshots a month at no charge and no card by creating a ScreenshotNeo account.

Frequently Asked Questions

Can I keep a redirect audit trail as well as the final screenshot?

Yes. Save page.url for the resulting location and separately append any redirectURL pairs observed in onResourceReceived. The two records answer different questions.

Does a 404 prevent page.render()?

Not automatically. SlimerJS can report success after receiving a 404 response, so your script must inspect recorded status codes and decide whether to render or reject the job.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is the promise API a drop-in replacement for PhantomJS scripts?

No. SlimerJS’s documented promise return is intended for SlimerJS and is not compatible with PhantomJS; callback style is the safer shared form for legacy code.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.