Nativefier: the setup order that holds up
The command itself is one line. That is not where the time goes. The time goes into building an app, using it for a week, discovering that the sign in page bounces out to the default browser, and running the whole thing again with one more flag. Then discovering the icon never got attached, and running it a third time.
That loop exists because of a structural property of the tool. Options are written into the generated application at build time. There is no preferences window inside the result, no config file to edit afterwards, no way to toggle a setting and restart. Whatever was decided at the moment of the build is what the app is. So the useful version of a setup guide is not a list of flags. It is an order of decisions, arranged so that the expensive ones get settled before the first build rather than after the third.
What is frozen and what is not
Before any command gets typed, it helps to see which column each choice lands in.
| Choice | After the build |
|---|---|
| Application name and icon | Rebuild or in place upgrade |
| Target architecture | Rebuild |
| Which URLs count as internal | Rebuild |
| Single instance behaviour, tray residency | Rebuild |
| Initial window size | Window can be dragged, default is fixed |
| Injected CSS or JavaScript | Rebuild |
| Login state and cache | Accumulates inside the app |
Six of those seven rows say rebuild. That ratio is the whole argument for planning the build instead of discovering it. A rebuild is not expensive in minutes, but it is expensive in attention, because each one interrupts whatever the app was supposed to be helping with.
The stated environment requirement is Node.js 16.16.0 or newer with npm 8.11.0 or newer. Installation is a global npm install, and the build form is the tool name followed by options, the target URL, and optionally a destination. Everything below is about filling in that options section in a sensible sequence.
Decide where the output lands, before anything else
With no destination argument, the app is written into whatever directory the command was run from. Running a quick test from a downloads folder puts a working application there, and it keeps working from there, which is exactly how a half finished experiment becomes permanent and then breaks when the folder gets cleaned out.
Two ways to control this exist. A destination path can be passed as the last argument. Alternatively, an environment variable named NATIVEFIER_APPS_DIR can be set once, and the documentation suggests adding an export line for it to a shell startup file pointing at the Applications folder inside the home directory. After that, every build lands in the right place without further thought.
Settling this first matters more than it looks. The in place upgrade path expects the generated app to still be sitting in the context where it was created, alongside the resources directory produced with it. Moving apps around after the fact undermines the one mechanism that makes rebuilding cheap later.
Confirm which machine the build is targeting
The architecture of the output follows the architecture of the Node.js binary doing the build, not the architecture of the Mac sitting on the desk. On Apple Silicon this produces a specific and quiet failure: if Node.js was installed through an x64 toolchain, the result is an x64 application running under translation. It launches, it works, and nothing announces the mismatch.
One command settles it. Printing process.arch from Node.js returns either arm64 or x64. If the answer is x64 on an Apple Silicon machine, either pass an explicit arm64 architecture flag or reinstall Node.js from an arm64 source. A universal build covering both is also available, and that option is only valid when targeting macOS.
The platform flag is a separate axis from the architecture flag, and conflating them is common. Platform selects the operating system the app is for. Architecture selects the processor. Printing process.platform confirms the current default. Getting this wrong produces an app that either refuses to launch or runs slower than it should for no visible reason.
Build once with no flags at all
With destination and target settled, resist configuring. Pass the URL and nothing else.
The reason is that the tool attempts to retrieve a name and an icon from the site itself. Seeing what it retrieves is faster than guessing what to override. Often the automatic name is fine and the icon is acceptable, which removes two flags from the final command. Where the automatic result is wrong, it is wrong in a specific way that makes the correct override obvious.
This throwaway build is also the login test. Open it, sign in, and use it the way it would be used on a normal working day. Watch for the moment when a click hands off to the default browser instead of staying inside the window. That moment is the single most useful piece of information available before the real build, and it feeds directly into the next decision.
Decide where links are allowed to go
This is where most setups actually fail, and it is worth understanding the default before overriding it.
Navigation is classified as internal or external. External navigation is handed to the default browser. By default the classification compares base domains with any www prefix stripped, so a site and its subdomains are internal to each other while an unrelated domain is external. For a single site with everything under one domain, the default is correct and needs no attention.
Authentication is where the default breaks, because sign in frequently happens on a different domain entirely. A list of known login pages is treated as internal ahead of any custom rule, and it covers the common identity providers including Google accounts, Microsoft login endpoints, Okta, Atlassian identity, and GitHub sessions. Sites built on those usually work without intervention, which is why the throwaway build above is the fastest way to find out whether intervention is needed.
When the identity provider is not on that list, a regular expression can be supplied to extend the internal set. For an internal admin console that should never open anything externally, a catch all pattern makes every destination internal. Going the other direction, base domain matching can be switched off so that only the explicit pattern and the login page list count, which is the right choice when subdomains genuinely belong to different services. A further option blocks external navigation outright and shows an error instead of handing off, which suits kiosk style deployments where wandering off the intended site is a defect rather than a convenience.
These three settings interact, and all three are frozen at build time. This is the decision worth slowing down for.
Decide how the window behaves and whether it stays resident
Frequency of use should drive these choices rather than preference.
For a site opened and closed many times a day, single instance behaviour prevents a new window from appearing on every Dock click and brings the existing one forward instead. Without it, the Dock icon quietly accumulates windows.
For a site that should never really close, tray residency keeps the app alive as a menu bar icon and stops the close button from quitting it. An optional argument starts it in the tray without showing a window at all, which suits something that should be running from login but not demanding attention. One documented limitation applies: building a macOS app with tray residency from a non macOS machine produces an invisible menu bar icon. Build Mac apps on a Mac.
Window geometry rounds out the group. Width and height set the initial size, with a default width of 1280 pixels. An always on top flag exists for reference material that needs to stay visible. Title bar style can be changed, and the documentation pairs that with injected CSS marking a region of the page as draggable, since hiding the title bar otherwise removes the only place the window can be grabbed. Those two belong together and should be decided together.
Read this before building a portable copy
A portable mode exists that stores cookies, cache, and other user data inside the application folder rather than in the usual per user location. It travels between machines intact. It also carries a warning that deserves to be read in full rather than summarised.
IMPORTANT SECURITY NOTICE: when creating a portable app, all data accumulated after running the app (including login information, cache, cookies), will be saved in the app folder. If this app is then shared with others, THEY WILL HAVE THAT ACCUMULATED DATA, POTENTIALLY INCLUDING ACCESS TO ANY ACCOUNTS YOU LOGGED INTO. Source: github.com
The documented distribution procedure is to build it, test it, delete both the app and its containing folder, rebuild it identically, and hand over the rebuilt copy without ever opening it. The practical rule underneath that procedure is simpler: a portable build for personal travel and a portable build for handing to someone else should never be the same artifact.
Write the command down
The last step of the setup is recording the exact command in a text file next to wherever the apps live.
Two reasons. The first is reconstruction. Since every option lives inside the binary rather than in a readable config, rebuilding in six months starts with an archaeology session unless the command was saved. A single saved line removes that entirely.
The second reason is the upgrade path. An in place upgrade flag can read the options out of an existing app and rebuild it with the current version of the tool. It works, and it also replaces the existing application in place, which is why the documentation recommends taking a backup or naming an alternate destination first. A saved command makes that mechanism optional rather than necessary, and optional is a better position to be in with an archived tool.
One related flag belongs here as well. Source files inside the generated app can be packed into an archive rather than left as readable files, which matters if injected CSS or JavaScript contains anything specific to an internal system. That choice is also frozen at build time, so it belongs on the list before the final build rather than after it.
What to change first
Settle the internal URL boundary before building the copy that gets kept, because it is both the least reversible decision and the one that only reveals itself after real use. Run the throwaway build, sign in completely, and watch where the handoff happens. If maintaining that loop for every site is not a good use of the week, Supported services shows the kinds of sites people most often pull out of a tab strip, Features covers what a wrapped window is expected to carry, and Kagemusha sets out the cost side once the decision list above stops being worth doing by hand.
Frequently asked questions
Can the app name or icon be changed after the app is built?
Not from inside the app. The options are written into the binary at build time and there is no settings panel to edit. The two routes are a fresh build or the in place upgrade flag, which reads existing options out of an app and rebuilds it. Since the upgrade replaces the application where it stands, taking a backup or specifying a different destination first is the documented precaution.
Sign in keeps opening in the default browser instead of staying in the window. What fixes it?
That is the internal versus external URL classification. By default only URLs sharing a base domain are treated as internal, so an identity provider on a separate domain gets handed off. A list of well known login pages is already treated as internal, covering most major providers. For anything outside that list, supply a regular expression extending the internal set and rebuild.
Why did an Intel app come out of an Apple Silicon Mac?
Because the output architecture follows the Node.js binary that ran the build, not the hardware. A Node.js install that came through an x64 toolchain produces an x64 app under translation, with no error to indicate it. Print process.arch to check, then either pass an explicit arm64 architecture or reinstall Node.js. A universal build is available for macOS targets if both are needed.
How many options should be set on the first build?
None. Pass only the URL, then look at the automatically retrieved name and icon and complete a full sign in. That single throwaway build supplies the information needed for the decisions that cannot be reversed later, namely the URL boundary, the architecture, and the residency behaviour. Window geometry and similar adjustable details can wait.