hexojs/hexo · error · Error
${magenta(tildify(publicDir))} is not a directory
Error message
${magenta(tildify(publicDir))} is not a directory What it means
Thrown by the generate console (lib/plugins/console/generate.ts:162) during firstGenerate(). Hexo stats the configured public_dir; if the path exists but is NOT a directory (it is a regular file, symlink to a file, socket, etc.), it throws. An ENOENT (missing) is tolerated and the directory is created, but a file-in-place is not.
Source
Thrown at lib/plugins/console/generate.ts:162
}
firstGenerate(): Promise<void> {
const { concurrency } = this;
const { route, log } = this.context;
const publicDir = this.context.public_dir;
const Cache = this.context.model('Cache');
// Show the loading time
const interval = prettyHrtime(process.hrtime(this.start));
log.info('Files loaded in %s', cyan(interval));
// Reset the timer for later usage
this.start = process.hrtime();
// Check the public folder
return stat(publicDir).then(stats => {
if (!stats.isDirectory()) {
throw new Error(`${magenta(tildify(publicDir))} is not a directory`);
}
}).catch(err => {
// Create public folder if not exists
if (err && err.code === 'ENOENT') {
return mkdirs(publicDir);
}
throw err;
}).then(() => {
const task = (fn, path) => () => fn.call(this, path);
const doTask = fn => fn();
const routeList = route.list();
const routeSet = new Set(routeList);
const publicFiles = Cache.filter(item => item._id.startsWith('public/')).map(item => item._id.substring(7));
const tasks = publicFiles.filter(path => !routeSet.has(path))
// Clean files
.map(path => task(this.deleteFile, path))
// Generate filesView on GitHub (pinned to 059cb17494)
Solutions
- Remove or rename the offending file: rm <public_dir>, then let Hexo recreate the directory.
- Check config.public_dir in _config.yml points to a folder name (default public), not a file path.
- If it is a symlink, point it at a directory or remove it.
- Clear CI caches that may have cached the path as a file.
Example fix
# before # a file named 'public' exists in the project root # shell rm public # then re-run hexo generate
Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
const publicDir = hexo.public_dir;
try {
const stats = fs.statSync(publicDir);
if (!stats.isDirectory()) {
throw new Error(`${publicDir} is a file (${stats.isFile() ? 'file' : 'other'}), remove it before generate`);
}
} catch (e) {
if (e.code !== 'ENOENT') throw e;
} Prevention
- Keep public_dir as a folder name (default 'public').
- Add a pre-generate check that removes a conflicting file at public_dir.
- Audit CI caches so they do not restore a file at the public path.
When it happens
Trigger: config.public_dir resolves to a path occupied by a regular file (e.g. someone ran touch public, or a build artifact file sits there); a symlink pointing to a file; public_dir misconfigured to a filename. stat() succeeds, isDirectory() is false.
Common situations: A previous step wrote a file named 'public' instead of a folder; CI cache restoring a file at that path; public_dir set to something like public/index.html by mistake; a deploy tool creating a file placeholder.
Related errors
- syntax highlighter ${name} is not registered
- Draft "${slug}" does not exist.
- Invalid config detected: "url" should be string, not ${typeo
- Invalid config detected: "url" should be a valid URL!
- Invalid config detected: "root" should be string, not ${type
AI-assisted analysis of hexojs/hexo@059cb17494 (2026-08-12).
Data as JSON: /api/errors/3094e6fd2630276c.
Report an issue: GitHub.