remix-run/react-router · error · CopyTemplateError
The provided template is not a valid local directory or tarb
Error message
The provided template is not a valid local directory or tarball.
What it means
Thrown by copyTemplateFromLocalFilePath() after the path is confirmed to exist but is neither a .tar.gz/.tgz tarball nor a directory. The function first checks the extension for tarball extraction, then fs.statSync().isDirectory() for a direct copy; any other file type (a regular file, a symlink to a file, etc.) reaches the throw. It indicates the local template target is an individual file rather than a project tree.
Source
Thrown at packages/create-react-router/copy-template.ts:139
) {
await copyTemplateFromRemoteTarball(url, destPath, options);
}
async function copyTemplateFromLocalFilePath(
filePath: string,
destPath: string,
): Promise<boolean> {
if (filePath.endsWith(".tar.gz") || filePath.endsWith(".tgz")) {
await extractLocalTarball(filePath, destPath);
return false;
}
if (fs.statSync(filePath).isDirectory()) {
// If our template is just a directory on disk, return true here, and we'll
// just copy directly from there instead of "extracting" to a temp
// directory first
return true;
}
throw new CopyTemplateError(
"The provided template is not a valid local directory or tarball.",
);
}
const pipeline = promisify(stream.pipeline);
async function extractLocalTarball(
tarballPath: string,
destPath: string,
): Promise<void> {
try {
await pipeline(
fs.createReadStream(tarballPath),
gunzip(),
tar.extract(destPath, { strip: 1 }),
);
} catch (error: unknown) {
throw new CopyTemplateError(View on GitHub (pinned to 1fd704a7da)
Solutions
- Point --template at the directory containing the project, not a file inside it: --template ./path/to/template-dir.
- If the template is a compressed archive, repackage it as .tar.gz or .tgz (tar -czf template.tar.gz -C template-dir .) and pass that path.
- If you only have a .zip, extract it first (unzip template.zip) then pass the extracted directory.
- Confirm the target with ls -la <path> and ensure it shows 'directory' (d prefix) before rerunning.
Example fix
// before create-react-router my-app --template ./template.zip // after (extract first, then point at the directory) unzip template.zip -d template-dir create-react-router my-app --template ./template-dir
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
function isLocalDirOrTarball(p: string): boolean {
if (!fs.existsSync(p)) return false;
if (p.endsWith('.tar.gz') || p.endsWith('.tgz')) return true;
try { return fs.statSync(p).isDirectory(); } catch { return false; }
}
// if (!isLocalDirOrTarball(template)) error before running the CLI Type guard
function isLocalTemplate(p: string): boolean {
return (p.endsWith('.tar.gz') || p.endsWith('.tgz')) || (fs.existsSync(p) && fs.statSync(p).isDirectory());
} Prevention
- Point --template at directories, not individual files.
- Keep tarballs in .tar.gz/.tgz format; extract zips before passing them.
- Run ls -la on the target to confirm it is a directory (d-prefixed) before scaffolding.
When it happens
Trigger: Passing --template ./README.md, --template ./package.json, or any single regular file that does not end in .tar.gz/.tgz. Also a broken symlink whose target resolves to a file, or a path whose extension was expected to be a tarball but was renamed (e.g. template.zip).
Common situations: User selects a file in their editor and pastes its path; template was zipped as .zip instead of .tar.gz; the template directory was replaced by a file of the same name; pointing at a package.json thinking it is the project root.
Related errors
- There was a problem extracting the file from the provided te
- "${template}" is an invalid template. Run "create-react-rout
- File collisions detected in a non-interactive environment
- Exiting to avoid overwriting files
- package.json does not exist in ${ctx.cwd}
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/0c9df3d8c9303573.
Report an issue: GitHub.