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 files

View on GitHub (pinned to 059cb17494)

Solutions

  1. Remove or rename the offending file: rm <public_dir>, then let Hexo recreate the directory.
  2. Check config.public_dir in _config.yml points to a folder name (default public), not a file path.
  3. If it is a symlink, point it at a directory or remove it.
  4. 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

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


AI-assisted analysis of hexojs/hexo@059cb17494 (2026-08-12). Data as JSON: /api/errors/3094e6fd2630276c. Report an issue: GitHub.