Install Node.js: Checking Button Behavior on a Server Without a Browser

Affiliate disclosure v3.1
Affiliate disclosure v3.2 (mobile)
Test environmentRHEL familyAlmaLinux 9.8root accountChecked 2026-10-19

To run JavaScript on a server, you first need to install Node.js. I installed Node.js 24.21.0 on a WordPress server running Nginx and Apache, and used jsdom 30.1.3 to check a web page’s button script without a browser.

The nodejs in the default repository is 16.20.2, but jsdom 30.1.3, which I use to check scripts, requires Node.js 22.22.2 or later. So I installed it from dnf module stream 24.

1️⃣ Why install Node.js and jsdom on a server

Checking button scripts on a server takes two things: Node.js and jsdom.

🔲 Node.js

JavaScript usually runs inside a browser, but once you install Node.js, the server can read and run JavaScript too.

Installing Node.js means putting the JavaScript runtime that used to live only in the browser onto the server as well. That is why Node.js is used to build web servers, to create commands that process many files at once, or to load packages such as jsdom and check pages. Ghost is also a CMS that runs on Node.js.

🔲 jsdom

jsdom is a package for Node.js that reads HTML and turns it into a document whose buttons can be pressed, just like in a browser. It builds the document structure (DOM) that a browser creates when it opens a page, but inside the server and without a screen, so scripts find buttons and change text as if they were in a real browser. Because no window opens, it runs with a single command on the server.

In other words, the checks a person used to do by opening a browser and clicking things one by one are handled by the server with a command. That is why jsdom is also used to test button behavior automatically after every fix, to pull out only content such as titles and links from a page, or to edit post HTML in bulk.

If you fetch a page with PHP or curl, you only see the HTML that was first sent. Text that changes after a button is pressed only appears once the script runs.

A browser goes through four steps: getting the page, reading the page, pressing the button and showing it on screen. jsdom runs every step except the last one on the server, so a button’s position and size are checked in a browser.

🧩 What a browser and jsdom do
Explainer diagram
🖥️ Runs on
Get the pageGets the HTML from the server
Read the pageWorks out what is where
Press the buttonScripts change the text
Show it on screenDraws position, size and fonts
Text change ✓Errors ✓Position · size ✓Position · size ✗ (check in a browser)
A browser goes through all four steps.
jsdom runs only the first three steps on the server.

2️⃣ Before installing: the default repository and module streams

Before running [dnf install nodejs], first check which version would be installed. The nodejs in the AlmaLinux 9.8 default repository is 16.20.2.

dnf list --available nodejs
Available Packages
nodejs.x86_64                    1:16.20.2-8.el9_4                     appstream

The module repository has four streams: 18, 20, 22 and 24. As of the check date, the 22 stream was at 22.23.2 and the 24 stream at 24.21.0.

dnf module list nodejs
nodejs 18     common [d], development, minimal, s2i Javascript runtime
nodejs 20     common [d], development, minimal, s2i Javascript runtime
nodejs 22     common [d], development, minimal, s2i Javascript runtime
nodejs 24     common [d], development, minimal, s2i Javascript runtime

According to the npm registry, jsdom 30.1.3 requires 22.22.2 or later in the 22 line, 24.15.0 or later in the 24 line, or 26 and above. Both the 22 and 24 streams meet this requirement.

3️⃣ Install Node.js: dnf module

This time I installed the 24 stream, and before running it I first checked the list of packages to be installed with the [--assumeno] option.

dnf module install nodejs:24 --assumeno

The list contained only five packages: nodejs, npm, nodejs-libs, nodejs-docs and nodejs-full-i18n. No web server, PHP or kernel packages were included. After checking, run the installation with the [-y] option.

dnf module install -y nodejs:24
Dependencies resolved.
==============================================================================================
 Package            Architecture  Version                                    Repository  Size
==============================================================================================
Installing group/module packages:
 nodejs             x86_64  1:24.21.0-1.module_el9.8.0+321+1555b6a2           appstream   70 k
 npm                noarch  1:11.19.0-1.24.21.0.1.module_el9.8.0+321+1555b6a2 appstream  2.1 M
Installing dependencies:
 nodejs-libs        x86_64  1:24.21.0-1.module_el9.8.0+321+1555b6a2           appstream   24 M
Installing weak dependencies:
 nodejs-docs        noarch  1:24.21.0-1.module_el9.8.0+321+1555b6a2           appstream  5.3 M
 nodejs-full-i18n   x86_64  1:24.21.0-1.module_el9.8.0+321+1555b6a2           appstream  8.9 M
Installing module profiles:
 nodejs/common                                                                                
Enabling module streams:
 nodejs                     24                                                                

Transaction Summary
==============================================================================================
Install  5 Packages

Total download size: 40 M
Installed size: 185 M

[Installing weak dependencies] lists the weak dependencies, packages that are good to install alongside. The download size is 40M and the installed size is 185M.

node -v
v24.21.0
npm -v
11.19.0
cat /etc/dnf/modules.d/nodejs.module
[nodejs]
name=nodejs
stream=24
profiles=common
state=enabled

node was installed as 24.21.0 and npm as 11.19.0. The only configuration file changed by the installation is [/etc/dnf/modules.d/nodejs.module], which records the 24 stream and the common profile as enabled.

4️⃣ Installing jsdom and checking a button script

jsdom is installed with npm into a working folder, and the working folder is created outside the web root.

mkdir ~/jsdom-test && cd ~/jsdom-test
npm init -y

[npm init -y] creates package.json with default values, and jsdom is then installed into the same folder.

npm install jsdom
added 36 packages, and audited 37 packages in 2s

8 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm ls jsdom
jsdom-test@1.0.0 /root/jsdom-test
└── jsdom@30.1.3

36 packages including jsdom 30.1.3 were installed, with 0 known vulnerabilities. The node_modules folder in the working folder is 26M.

node -e 'const { JSDOM } = require(`jsdom`); console.log(new JSDOM(`<h1>Hello</h1>`).window.document.querySelector(`h1`).textContent)'
Hello

If jsdom reads the HTML made of a single h1 tag as a document and prints the text inside it, [Hello], the check is done.

Checking a button script means confirming on the server that the text changes correctly after a click. As an example, I created an HTML file in the working folder where clicking a [Copy] button changes its text to [Copied].

cat btn.html
<button id="copy">Copy</button>
<script>
document.getElementById('copy').addEventListener('click', function () {
  this.textContent = 'Copied';
});
</script>

The check script reads the HTML file with jsdom, runs its scripts and prints the text before and after the click. Script errors are printed on lines starting with [Error:].

cat check.js
const fs = require('fs');
const { JSDOM, VirtualConsole } = require('jsdom');

const vc = new VirtualConsole();
vc.on('jsdomError', (e) => console.log('Error:', e.message));

const dom = new JSDOM(fs.readFileSync(process.argv[2], 'utf8'), {
  runScripts: 'dangerously',
  virtualConsole: vc,
});
const btn = dom.window.document.querySelector('#copy');
console.log('Before click:', btn.textContent);
btn.click();
console.log('After click:', btn.textContent);
node check.js btn.html
Before click: Copy
After click: Copied

The text changes to [Copied] after the click, which confirms that the button script works.

A single typo in the script changes the result. I ran the same check on a file where the capital I in getElementById was replaced with a lowercase l.

sed 's/getElementById/getElementByld/' btn.html > btn-broken.html
node check.js btn-broken.html
Error: Uncaught [TypeError: document.getElementByld is not a function]
Before click: Copy
After click: Copy

An error is printed on the first line and the script stops, so the text stays [Copy] even after the click.

I gave the button a width of 120px and a height of 40px with CSS and then printed its size.

cat size.js
const { JSDOM } = require('jsdom');
const dom = new JSDOM('<button id="copy" style="width:120px;height:40px">Copy</button>');
const btn = dom.window.document.querySelector('#copy');
console.log('height:', btn.offsetHeight, '/ width:', btn.getBoundingClientRect().width);
node size.js
height: 0 / width: 0

jsdom has no step that draws on the screen, so the height and width are printed as 0.

🔢 FAQ & Recommended Content

The npm registry lists Node.js 22.22.2 or later, 24.15.0 or later, or 26 and above as the versions jsdom 30.1.3 requires, so the 18 and 20 streams do not meet this condition.

[which node] does not show a node that is not on the PATH. On the server I checked, [which node] returned nothing, yet Node.js 18.4.0 installed by the pip package nodejs-bin was inside a user folder.

[d] marks the default; when it follows the common profile, the common profile is installed if you install without specifying a profile.

🧭 Package management: recommended reading 9 posts · 2 references
🗂️ Package management
📄 yum command 📄 yum-config-manager command 📄 RPM vs YUM 📄 apt · apt-get commands 📄 Installing snapd
📄 Install Node.js 📄 Installing Nginx · PHP on CentOS 7 📄 Installing phpMyAdmin 📄 Installing and configuring PHP-FPM
📡 DNF Module Command 📡 Node.js Previous Releases

ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.

Leave a Reply

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

Prove your humanity: 6   +   6   =