Recommended Free Tools
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
- Open the starting URL.
page.open(startUrl, callback)starts navigation and returns before the page is ready. - Wait for the callback or promise continuation. Any URL read or render issued immediately after
page.open()can run before navigation finishes. - 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. - Optionally collect redirect metadata. In
onResourceReceived, a response can exposeredirectURL. Store those pairs when you need a diagnostic chain, but do not assume every redirect will provide that optional field. - Set the viewport, then wait for reflow. Assigning
viewportSizetriggers an asynchronous layout update. - 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.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minute#1 Best Overall
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.
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.
Rank #2
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.
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.
Rank #3
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.
- 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.
Rank #4
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.
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.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Best Value
Operational guidance for a legacy capture job
- Log both URLs: the requested start URL and
page.urlafter load. - Log redirect records: retain each
from/topair 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.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteOther 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.
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.
Quick Recap
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.




