blogstart
Version:
Automatically setup SEO-optimized blog routes for Next.js projects
484 lines (421 loc) β’ 17.3 kB
JavaScript
const fs = require("fs");
const path = require("path");
// Get the directory where the package is being installed
const projectRoot = process.cwd();
// Add error handling for ES modules projects
process.on("uncaughtException", (error) => {
console.error("β SlashBlog Start setup failed:", error.message);
console.log(
"π‘ Try running manually: node node_modules/@slashblog/start/setup.js"
);
process.exit(1);
});
process.on("unhandledRejection", (reason, promise) => {
console.error("β SlashBlog Start setup failed:", reason);
console.log(
"π‘ Try running manually: node node_modules/@slashblog/start/setup.js"
);
process.exit(1);
});
// Find the package directory (where templates are located)
function getPackageDir() {
console.log("π Looking for package directory...");
console.log("π __dirname:", __dirname);
console.log("π projectRoot:", projectRoot);
// First try __dirname (local development or when run directly)
const templatePath1 = path.join(__dirname, "templates");
console.log("π Checking:", templatePath1);
if (fs.existsSync(templatePath1)) {
console.log("β
Found templates at:", templatePath1);
return __dirname;
}
// Try node_modules/@slashblog/start (when installed via npm)
const nodeModulesPath = path.join(
projectRoot,
"node_modules",
"@slashblog",
"start"
);
const templatePath2 = path.join(nodeModulesPath, "templates");
console.log("π Checking:", templatePath2);
if (fs.existsSync(templatePath2)) {
console.log("β
Found templates at:", templatePath2);
return nodeModulesPath;
}
// Try relative to this script location
const scriptDir = path.dirname(
require.main ? require.main.filename : __filename
);
const templatePath3 = path.join(scriptDir, "templates");
console.log("π Checking:", templatePath3);
if (fs.existsSync(templatePath3)) {
console.log("β
Found templates at:", templatePath3);
return scriptDir;
}
// Try one level up from __dirname
const parentDir = path.dirname(__dirname);
const templatePath4 = path.join(parentDir, "templates");
console.log("π Checking:", templatePath4);
if (fs.existsSync(templatePath4)) {
console.log("β
Found templates at:", templatePath4);
return parentDir;
}
// List what's actually in __dirname for debugging
console.log("π Contents of __dirname:", fs.readdirSync(__dirname));
console.log("π Contents of projectRoot:", fs.readdirSync(projectRoot));
throw new Error("Could not find @slashblog/start templates directory");
}
// Check if it's a TypeScript project
function isTypeScriptProject() {
return fs.existsSync(path.join(projectRoot, "tsconfig.json"));
}
// Check if app directory exists (App Router)
function isAppRouter() {
return (
fs.existsSync(path.join(projectRoot, "app")) ||
fs.existsSync(path.join(projectRoot, "src", "app"))
);
}
// Check if pages directory exists (Pages Router)
function isPagesRouter() {
return (
fs.existsSync(path.join(projectRoot, "pages")) ||
fs.existsSync(path.join(projectRoot, "src", "pages"))
);
}
// Get the correct app or pages directory path
function getAppDir() {
if (fs.existsSync(path.join(projectRoot, "app"))) {
return path.join(projectRoot, "app");
}
if (fs.existsSync(path.join(projectRoot, "src", "app"))) {
return path.join(projectRoot, "src", "app");
}
if (fs.existsSync(path.join(projectRoot, "pages"))) {
return path.join(projectRoot, "pages");
}
if (fs.existsSync(path.join(projectRoot, "src", "pages"))) {
return path.join(projectRoot, "src", "pages");
}
return null;
}
// Create blog directory structure
function createBlogStructure() {
console.log("π Detecting project structure...");
const isTS = isTypeScriptProject();
const fileExtension = isTS ? ".tsx" : ".jsx";
console.log(`π TypeScript project: ${isTS ? "Yes" : "No"}`);
console.log(`π File extension: ${fileExtension}`);
let blogDir;
let routerType;
// Determine router type and set blog directory
const appDirPath = getAppDir();
if (!appDirPath) {
console.log(
"β No Next.js app or pages directory found. Please make sure this is a Next.js project."
);
return;
}
if (isAppRouter()) {
blogDir = path.join(appDirPath, "blog");
routerType = "app";
console.log("π― Detected: App Router");
} else if (isPagesRouter()) {
blogDir = path.join(appDirPath, "blog");
routerType = "pages";
console.log("π― Detected: Pages Router");
} else {
console.log(
"β No Next.js app or pages directory found. Please make sure this is a Next.js project."
);
return;
}
// Check if blog directory already exists
if (fs.existsSync(blogDir)) {
console.log("β οΈ Blog directory already exists at:", blogDir);
console.log(
"π Overwriting existing blog files with SlashBlog Start templates..."
);
console.log(
"π‘ Your existing files will be backed up with .backup extension"
);
// Backup existing files
const existingFiles = fs.readdirSync(blogDir, { recursive: true });
existingFiles.forEach((file) => {
const filePath = path.join(blogDir, file);
if (
(fs.statSync(filePath).isFile() && file.endsWith(".tsx")) ||
file.endsWith(".jsx")
) {
const backupPath = filePath + ".backup";
fs.copyFileSync(filePath, backupPath);
console.log(`π Backed up: ${file} β ${file}.backup`);
}
});
} else {
console.log(`π Creating blog directory: ${blogDir}`);
// Create blog directory
fs.mkdirSync(blogDir, { recursive: true });
}
// Sample blog data is not needed since content is fetched remotely
// createSampleBlogData();
if (routerType === "app") {
// App Router structure
createAppRouterStructure(blogDir, isTS, fileExtension);
} else {
// Pages Router structure
createPagesRouterStructure(blogDir, isTS, fileExtension);
}
console.log("β
Blog setup completed successfully!");
console.log("π Created blog routes in:", blogDir);
console.log(
`π Blog configured for website ID: ${process.env.BLOGSTART_WEBSITE_ID}`
);
console.log("π You can now access your blog at /blog");
console.log("");
console.log("π Next steps:");
console.log("1. Ensure you have Tailwind CSS installed for styling");
console.log("2. Your blog will automatically fetch the latest content");
console.log("3. Build and deploy your Next.js application");
console.log("");
console.log("π See USAGE.md for detailed instructions");
}
function createAppRouterStructure(blogDir, isTS, fileExtension) {
try {
console.log("ποΈ Creating App Router structure...");
// Create [slug] directory
const slugDir = path.join(blogDir, "[slug]");
console.log(`π Creating slug directory: ${slugDir}`);
fs.mkdirSync(slugDir, { recursive: true });
const packageDir = getPackageDir();
console.log(`π¦ Package directory: ${packageDir}`);
// Check if templates exist
const indexTemplatePath = path.join(
packageDir,
"templates",
"app-router",
`page${isTS ? ".tsx" : ".jsx"}`
);
const slugTemplatePath = path.join(
packageDir,
"templates",
"app-router",
`slug-page${isTS ? ".tsx" : ".jsx"}`
);
console.log(`π Index template: ${indexTemplatePath}`);
console.log(`π Slug template: ${slugTemplatePath}`);
console.log(
`β
Index template exists: ${fs.existsSync(indexTemplatePath)}`
);
console.log(`β
Slug template exists: ${fs.existsSync(slugTemplatePath)}`);
// Check if templates exist before reading
if (!fs.existsSync(indexTemplatePath)) {
throw new Error(`Index template not found: ${indexTemplatePath}`);
}
if (!fs.existsSync(slugTemplatePath)) {
throw new Error(`Slug template not found: ${slugTemplatePath}`);
}
// Read templates
console.log("π Reading index template...");
const blogIndexTemplate = fs.readFileSync(indexTemplatePath, "utf8");
console.log("π Reading slug template...");
const blogSlugTemplate = fs.readFileSync(slugTemplatePath, "utf8");
console.log("β
Templates read successfully");
// Replace placeholder URL with Firebase Cloud Storage URL
const websiteId = process.env.BLOGSTART_WEBSITE_ID;
const firebaseBaseUrl = `https://firebasestorage.googleapis.com/v0/b/slashblog-ai.firebasestorage.app/o/blogs%2Fblog-content-${websiteId}.json?alt=media`;
const updatedIndexTemplate = blogIndexTemplate
.replace(/WEBSITE_ID_PLACEHOLDER/g, websiteId)
.replace(/FIREBASE_BASE_URL_PLACEHOLDER/g, firebaseBaseUrl);
const updatedSlugTemplate = blogSlugTemplate
.replace(/WEBSITE_ID_PLACEHOLDER/g, websiteId)
.replace(/FIREBASE_BASE_URL_PLACEHOLDER/g, firebaseBaseUrl);
// Create files
const indexFilePath = path.join(blogDir, `page${fileExtension}`);
const slugFilePath = path.join(slugDir, `page${fileExtension}`);
console.log(`πΎ Creating index file: ${indexFilePath}`);
console.log(`πΎ Creating slug file: ${slugFilePath}`);
fs.writeFileSync(indexFilePath, updatedIndexTemplate);
console.log(`β
Index file created: ${indexFilePath}`);
fs.writeFileSync(slugFilePath, updatedSlugTemplate);
console.log(`β
Slug file created: ${slugFilePath}`);
fs.writeFileSync(slugFilePath, updatedSlugTemplate);
console.log("β
App Router files created successfully");
} catch (error) {
console.error("β Error creating App Router structure:", error.message);
throw error;
}
}
// Create sample blog data file
function createSampleBlogData() {
const sampleDataPath = path.join(
projectRoot,
"public",
"sample-blog-posts.json"
);
// Create public directory if it doesn't exist
const publicDir = path.join(projectRoot, "public");
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
const sampleData = {
posts: [
{
keyword: "website audit service",
platform: "Shopify",
slug: "shopify-website-audit-service",
title: "Shopify Website Audit Service",
description:
"Complete audit of your Shopify site, combining SEO, performance, and layout analysis. From meta tags and page speed to core web vitals and mobile usability, you'll get a holistic score and detailed checklist of what to improve.",
cta: "Audit your Shopify site now β",
relatedPages: [
{ slug: "shopify-website-audit", title: "Shopify Website Audit" },
{
slug: "seo-shopify-website-audit",
title: "SEO Shopify Website Audit",
},
],
content: {
intro:
"A full audit of your Shopify site is essential if you're serious about growth. SEO, structure, mobile usabilityβall of it needs regular checks to stay ahead.",
platformImpact:
"Shopify users often deal with platform-specific limitations or quirks. Whether it's bloated plugins, rigid themes, or hidden performance drains, these affect how your site ranks and converts.",
commonMistakes: [
"Relying too heavily on third-party apps or plugins that slow down load times",
"Neglecting image optimization or lazy loading",
"Using default Shopify themes without proper structure",
"Skipping mobile responsiveness testing",
"Missing alt tags, meta titles, or page hierarchy issues",
],
howWeHelp:
"We run a custom-tailored audit for Shopify websites. We scan for performance issues, SEO flaws, and layout inefficiencies. You'll get a prioritized list of improvements and real data backing every suggestion.",
},
},
{
keyword: "nextjs seo optimization",
platform: "Next.js",
slug: "nextjs-seo-optimization",
title: "Next.js SEO Optimization",
description:
"Maximize your Next.js app's search engine visibility with our SEO optimization service. We handle metadata, structured data, and performance for better rankings.",
cta: "Boost your Next.js SEO β",
relatedPages: [
{ slug: "nextjs-metadata", title: "Next.js Metadata Guide" },
],
content: {
intro:
"Next.js offers powerful SEO capabilities, but many developers don't leverage them fully. Proper SEO setup can dramatically improve your search rankings.",
platformImpact:
"Next.js developers often focus on functionality over SEO. Without proper metadata, structured data, and performance optimization, even great apps can struggle in search results.",
commonMistakes: [
"Missing or incorrect metadata setup",
"Poor URL structure and routing",
"No structured data implementation",
"Ignoring Core Web Vitals",
"Inadequate image optimization",
],
howWeHelp:
"We audit your Next.js application for SEO best practices, implement proper metadata generation, optimize performance, and ensure your app is search engine friendly.",
},
},
],
};
console.log(`π Creating sample blog data: ${sampleDataPath}`);
fs.writeFileSync(sampleDataPath, JSON.stringify(sampleData, null, 2));
console.log("β
Sample blog data created");
}
function createPagesRouterStructure(blogDir, isTS, fileExtension) {
try {
console.log("ποΈ Creating Pages Router structure...");
const packageDir = getPackageDir();
console.log(`π¦ Package directory: ${packageDir}`);
// Check if templates exist
const indexTemplatePath = path.join(
packageDir,
"templates",
"pages-router",
`index${isTS ? ".tsx" : ".jsx"}`
);
const slugTemplatePath = path.join(
packageDir,
"templates",
"pages-router",
`[slug]${isTS ? ".tsx" : ".jsx"}`
);
console.log(`π Index template: ${indexTemplatePath}`);
console.log(`π Slug template: ${slugTemplatePath}`);
console.log(
`β
Index template exists: ${fs.existsSync(indexTemplatePath)}`
);
console.log(`β
Slug template exists: ${fs.existsSync(slugTemplatePath)}`);
// Read templates
const blogIndexTemplate = fs.readFileSync(indexTemplatePath, "utf8");
const blogSlugTemplate = fs.readFileSync(slugTemplatePath, "utf8");
// Replace placeholder URL with Firebase Cloud Storage URL
const websiteId = process.env.BLOGSTART_WEBSITE_ID;
const firebaseBaseUrl = `https://firebasestorage.googleapis.com/v0/b/slashblog-ai.firebasestorage.app/o/blogs%2Fblog-content-${websiteId}.json?alt=media`;
const updatedIndexTemplate = blogIndexTemplate
.replace(/WEBSITE_ID_PLACEHOLDER/g, websiteId)
.replace(/FIREBASE_BASE_URL_PLACEHOLDER/g, firebaseBaseUrl);
const updatedSlugTemplate = blogSlugTemplate
.replace(/WEBSITE_ID_PLACEHOLDER/g, websiteId)
.replace(/FIREBASE_BASE_URL_PLACEHOLDER/g, firebaseBaseUrl);
// Create files
const indexFilePath = path.join(blogDir, `index${fileExtension}`);
const slugFilePath = path.join(blogDir, `[slug]${fileExtension}`);
console.log(`πΎ Creating index file: ${indexFilePath}`);
console.log(`πΎ Creating slug file: ${slugFilePath}`);
fs.writeFileSync(indexFilePath, updatedIndexTemplate);
fs.writeFileSync(slugFilePath, updatedSlugTemplate);
console.log("β
Pages Router files created successfully");
} catch (error) {
console.error("β Error creating Pages Router structure:", error.message);
throw error;
}
}
// Main setup function
function setup() {
console.log("π Setting up @slashblog/start...");
console.log("π Project root:", projectRoot);
console.log("π Node version:", process.version);
console.log("π Platform:", process.platform);
try {
// Check if this is a Next.js project
const packageJsonPath = path.join(projectRoot, "package.json");
if (!fs.existsSync(packageJsonPath)) {
console.log(
"β No package.json found. Please make sure this is a Node.js project."
);
return;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const hasNext =
packageJson.dependencies?.next || packageJson.devDependencies?.next;
if (!hasNext) {
console.log(
"β Next.js not found in dependencies. Please make sure this is a Next.js project."
);
return;
}
// Check if we can find templates
try {
const packageDir = getPackageDir();
console.log("π¦ Found package templates at:", packageDir);
} catch (error) {
console.log(
"β Could not find @slashblog/start templates:",
error.message
);
console.log("π Current directory:", projectRoot);
console.log("π Script directory:", __dirname);
return;
}
createBlogStructure();
} catch (error) {
console.log("β Setup failed:", error.message);
console.log("Please check the error above and try again.");
}
}
// Run setup
setup();