https://github.com/osisdie/node-seo-for-fun
A configurable Node.js SEO validator that parses HTML DOM, checks against customizable rules, and reports actionable recommendations. Supports file and stream I/O with flexible output options.
https://github.com/osisdie/node-seo-for-fun
code-for-fun demo dom-analysis html-parser javascript nodejs seo seo-tool seo-validator vercel
Last synced: 7 days ago
JSON representation
A configurable Node.js SEO validator that parses HTML DOM, checks against customizable rules, and reports actionable recommendations. Supports file and stream I/O with flexible output options.
- Host: GitHub
- URL: https://github.com/osisdie/node-seo-for-fun
- Owner: osisdie
- License: mit
- Created: 2018-09-13T22:45:05.000Z (almost 8 years ago)
- Default Branch: main
- Last Pushed: 2026-05-24T04:55:56.000Z (about 1 month ago)
- Last Synced: 2026-05-24T06:36:34.007Z (about 1 month ago)
- Topics: code-for-fun, demo, dom-analysis, html-parser, javascript, nodejs, seo, seo-tool, seo-validator, vercel
- Language: HTML
- Homepage: https://node-seo-for-fun.vercel.app
- Size: 744 KB
- Stars: 1
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# node-seo-for-fun
[](https://github.com/osisdie/node-seo-for-fun/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
[](https://github.com/osisdie/node-seo-for-fun/commits/main)
A configurable Node.js SEO validator that parses HTML DOM, checks against customizable rules, and reports actionable recommendations. Supports file and stream I/O with flexible output options.
*Series of code_for_fun*
## Features
**[Live Demo](https://node-seo-for-fun.vercel.app)** | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fosisdie%2Fnode-seo-for-fun)
## Quick Example
```
$ node -e "
const fs = require('fs');
const { SEOValidator } = require('./lib/seo/seo_validator');
const { AppUtil } = require('./lib/app_util');
const { RuleInputEnum, RuleOutputEnum } = require('./lib/models/app_enum');
let readStream = fs.createReadStream('test/input/https___google_com_tw');
let validator = new SEOValidator()
.includeRules([1, 2, 3, 4, 5])
.setReader(AppUtil.createReader({ kind: RuleInputEnum.stream, stream: readStream }))
.setWriter(AppUtil.createWriter({ kind: RuleOutputEnum.console }));
validator.validate().then(result => console.log(result.data));
"
```
**Output:**
```
This HTML without tag
This HTML without tag
This HTML without tag
```
## Built-in Rules
| Rule | Checks | Severity |
|------|--------|----------|
| **Rule 1** | `
` tags must have `alt` attribute | Accessibility + SEO |
| **Rule 2** | `` tags must have `rel` attribute | SEO link signals |
| **Rule 3** | `` must contain ``, ``, `` | Critical SEO |
| **Rule 4** | No more than 15 `` tags | Content quality |
| **Rule 5** | Only one `` tag allowed | SEO heading structure |
| **Rule 101** | `` must exist (custom example) | Crawl control |
## Prerequisites
- Node.js 20.0 or higher
## Installation
```sh
git clone https://github.com/osisdie/node-seo-for-fun.git
cd node-seo-for-fun
npm install
```
### Run Locally (Web UI)
```sh
npm start # http://localhost:3000
# or with hot-reload:
npm run dev
```
Open `http://localhost:3000` to use the web-based SEO validator.
### Deploy to Vercel
```sh
npx vercel
```
Or click the **Deploy with Vercel** button above for one-click deployment.
## Config Your Rules
SEO rules are defined in the config file (**default**: `conf/config.json`)
### Default SEO syntax patterns
```json
"seo": {
"pattern": {
"existsTag": {
"xpath": "{{root}} {{tag}}",
"msg": "This HTML without <{{tag}}> tag"
},
"existsAttr": {
"xpath": "{{root}} {{tag}}[{{attr}}]",
"msg": "This HTML without <{{tag}} {{attr}}> tag"
},
"existsNoAttr": {
"xpath": "{{root}} {{tag}}:not([{{attr}}])",
"msg": "This HTML without <{{tag}} {{attr}}> tag"
},
"existsAttrVal": {
"xpath": "{{root}} {{tag}}[{{attr}}*={{value}}]",
"msg": "This HTML without <{{tag}} {{attr}}=\"{{value}}\"> tag"
},
"tagCountLessThan": {
"xpath": "{{root}} {{tag}}",
"msg": "This HTML have more than {{max}} <{{tag}}> tag"
}
}
}
```
### Predefined SEO rules 1~5 and custom rule 101
```json
"seo": {
"rules": {
"rule1": {
"ruleFor": [
{
"pattern": "seo:pattern:existsNoAttr",
"fn": "checkShouldNotExist",
"root": "html",
"tag": "img",
"attr": "alt"
}
]
},
"rule2": {
"ruleFor": [
{
"pattern": "seo:pattern:existsNoAttr",
"fn": "checkShouldNotExist",
"root": "html",
"tag": "a",
"attr": "rel"
}
]
},
"rule3": {
"ruleFor": [
{
"pattern": "seo:pattern:existsTag",
"fn": "checkShouldExist",
"root": "head",
"tag": "title",
"min": 1,
"max": 1
},
{
"pattern": "seo:pattern:existsAttrVal",
"fn": "checkShouldExist",
"root": "head",
"tag": "meta",
"attr": "name",
"value": "description",
"min": 1,
"max": 1
},
{
"pattern": "seo:pattern:existsAttrVal",
"fn": "checkShouldExist",
"root": "head",
"tag": "meta",
"attr": "name",
"value": "keywords",
"min": 1,
"max": 1
}
]
},
"rule4": {
"ruleFor": [
{
"pattern": "seo:pattern:tagCountLessThan",
"fn": "checkMaxOccurrence",
"root": "html",
"tag": "strong",
"max": 15,
"min": 0
}
]
},
"rule5": {
"ruleFor": [
{
"pattern": "seo:pattern:tagCountLessThan",
"fn": "checkMaxOccurrence",
"root": "html",
"tag": "h1",
"max": 1,
"min": 0
}
]
},
"rule101": {
"ruleFor": [
{
"pattern": "seo:pattern:existsAttrVal",
"fn": "checkShouldExist",
"root": "head",
"tag": "meta",
"attr": "name",
"value": "robots",
"min": 1,
"max": 1
}
]
}
}
}
```
## Unit Test
Run all tests:
```sh
npm test
```
**Sample output** (57 tests):
```
AppUtil() requires(/lib/app_util.js)
config
✔ path conf/config.json should exist
Function getCfgVal()
version
✔ app:version should be 0.1.0
SEOValidator() requires(/lib/seo/seo_validator.js)
Function validate()
pass, input:file, output:file
✔ rule1 should have 0 warning(s)
✔ rule2 should have 0 warning(s)
...
NOT pass, input:stream, output:console
✔ https://google.com.tw returns 3 warning(s)
SingleRuleParser() requires(/lib/seo/seo_validator.js)
Function checkConfigSyntax()
✔ correctly syntax (×11)
Function analysis()
✔ should return isSuccess with/without warnings (×14)
57 passing (2s)
```
### Test individual modules
```sh
# AppUtil config tests
npm test ./test/AppUtil_test.js
# Reader/Writer I/O tests
npm test ./test/app_fs_test.js
# Single rule syntax validation
npm test ./test/SingleRuleParser_test.js
# Full SEO validator integration tests
npm test ./test/SEOValidator_test.js
```
## Usage Example
```js
const fs = require('fs')
const { SEOValidator } = require('./lib/seo/seo_validator')
const { AppUtil } = require('./lib/app_util')
const { RuleInputEnum, RuleOutputEnum } = require('./lib/models/app_enum')
let readStream = fs.createReadStream('test/input/https___google_com_tw')
let validator = new SEOValidator()
.includeRules([1, 2, 3, 4, 5])
.setReader(AppUtil.createReader({ kind: RuleInputEnum.stream, stream: readStream }))
.setWriter(AppUtil.createWriter({ kind: RuleOutputEnum.file, path: 'test/output/result.out' }))
validator.validate()
.then(result => {
console.log(result.data)
// ['This HTML without tag',
// 'This HTML without tag',
// 'This HTML without tag']
})
```
## Architecture
```
node-seo-for-fun/
├── conf/config.json # SEO rules & patterns configuration
├── lib/
│ ├── app_util.js # Config loader & utility factory
│ ├── core/app_fs.js # ReaderBase / WriterBase (File, Stream, Console)
│ ├── models/app_enum.js # RuleInputEnum, RuleOutputEnum
│ └── seo/
│ ├── seo_validator.js # SEOValidator (high-level orchestrator)
│ └── seo_rule.js # SingleRuleParser / SingleRuleParserBase
├── test/
│ ├── input/ # Test HTML files (pass / not_pass per rule)
│ └── *.js # Mocha test suites
└── .github/workflows/ci.yml # CI: Node 20 + 22
```
## Create Your Own Custom Rule
You can easily create a new rule:
- Rule number should start after 101 (1~100 are reserved for system default rules). Prefix with `rule`, e.g. `rule101`.
- Combine your **tag**, **attribute**, **value**, or even **occurrences** as your new rule content.
- The **pattern** property is a config path, e.g. `"seo:pattern:existsAttrVal"` points to the DOM selector and alert message template.
- The **fn** property specifies the validation method in `SingleRuleParser` / `SingleRuleParserBase` (you can create custom validation functions if needed).
```json
"seo": {
"rules": {
"rule101": {
"ruleFor": [
{
"pattern": "seo:pattern:existsAttrVal",
"fn": "checkShouldExist",
"root": "head",
"tag": "meta",
"attr": "name",
"value": "robots",
"min": 1,
"max": 1
}
]
}
}
}
```
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on adding new rules and submitting PRs.
## License
[MIT](LICENSE)
*Enjoy this **node-seo-for-fun** project!*