前言
nextjs是一款定义为全栈开发的框架,具备nodejs后端的特性, 也就是支持SSR,官网也说能支持SSG,懂点前端知识再加点nodejs知识就能写一个全栈应用.
背景
公司在逐渐发展,需要做一个公司的论坛,这个任务页面部分交给我了,需要每一个文章都需要在搜索引擎被搜索到SSR(其实我感觉jsp的SSR效果是不是更好捏0_0).
项目搭建
结合上面,我也懒的废话了,开始搭建项目(时间2025年7月5号),我考虑的是
nextjs@14+antd+tiptap+zustand+sass+react-query+react-infinite-scroll-component
目录结构
总结
看到这个目录结构,基本就会了百分之80了,可能你看到这个Deploy.js不太熟悉代码如下
const path = require('path');
const Client = require('ssh2').Client;
const outDir = require("./build/outDir")
require('dotenv').config({ path: '.env.development' });
// SSH连接配置
const sshConfig = {
host: process.env.SSHHOST ,
port: process.env.SSHPORT ,
username: process.env.SSHUSER ,
password: process.env.SSHPASSWORD ,
};
// 本地目录路径和远程目录路径
const localDir = __dirname;
const remoteDir = process.env.REMOTE_DIR;
// 创建SSH连接
const conn = new Client();
// 新增:递归创建远程目录的函数
const ensureRemoteDir = (sftp, remotePath) => {
return new Promise((resolve, reject) => {
// 先检查目录是否存在
sftp.stat(remotePath, (statErr, stats) => {
if (!statErr && stats.isDirectory()) {
// 目录已存在
resolve();
return;
}
// 目录不存在,创建它
sftp.mkdir(remotePath, (err) => {
if (err) {
if (err.code === 4 || err.code === 2) { // 目录不存在或父目录不存在
const parentDir = path.dirname(remotePath);
if (parentDir !== remotePath && parentDir !== '/') {
ensureRemoteDir(sftp, parentDir)
.then(() => ensureRemoteDir(sftp, remotePath))
.then(resolve)
.catch(reject);
} else {
reject(err);
}
} else if (err.code === 11) {
// 目录已存在(竞态条件)
resolve();
} else {
reject(err);
}
} else {
console.log(`创建远程目录:${remotePath}`);
resolve();
}
});
});
});
};
// 监听ready事件
conn.on('ready', () => {
console.log('SSH连接成功');
const execDir = remoteDir + outDir
// 将本地目录下的所有文件上传至服务器上指定目录
const uploadPromise = [];
conn.sftp((err, sftp) => {
if (err) throw err;
const files = [outDir];
const uploadFile = (file) => {
return new Promise((resolve, reject) => {
try {
const localFilePath = localDir + '/' + file;
const remoteFilePath = remoteDir + '/' + file;
// 新增:确保远程目录存在
const remoteFileDir = path.dirname(remoteFilePath);
ensureRemoteDir(sftp, remoteFileDir).then(() => {
const readStream = fs.createReadStream(localFilePath);
const writeStream = sftp.createWriteStream(remoteFilePath);
writeStream.on('close', () => {
console.log(`文件 ${file} 上传成功`);
resolve();
});
writeStream.on('error', (err) => {
console.log(`文件 ${file} 上传失败:${err}`);
reject(err);
});
readStream.pipe(writeStream);
}).catch(reject);
} catch (error) {
reject(error);
}
});
}
const uploadDir = (files) => {
files.forEach((file) => {
// 修复:使用正确的文件路径
const fullPath = localDir + '/' + file;
// 检查是否存在文件
const isExist = fs.existsSync(fullPath);
if (!isExist) {
console.log(`文件 ${file} 不存在`);
} else {
try {
const stat = fs.lstatSync(fullPath);
if (stat.isDirectory()) {
const dirFiles = fs.readdirSync(fullPath);
uploadDir(dirFiles.map((dirFile) => file + '/' + dirFile));
} else if (stat.isFile()) {
uploadPromise.push(uploadFile(file));
}
} catch (error) {
console.log(`处理文件 ${file} 时出错:${error.message}`);
}
}
});
}
uploadDir(files);
Promise.all(uploadPromise).then(() => {
console.log('所有文件上传成功');
// 执行SSH命令
conn.exec(`cd ${execDir} && npm i --silent && pm2 del all`, (err, stream) => {
if (err) throw err;
stream.on('close', (code, signal) => {
console.log(`npm install 命令执行完毕,退出码: ${code}`);
// npm install 完成后执行其他命令
conn.exec(`ls -l ${execDir} && cd ${execDir} && pm2 start ecosystem.config.js`, (err, stream) => {
if (err) throw err;
stream.on('close', () => {
console.log('所有远程命令执行完毕');
conn.end();
}).on('data', (data) => {
console.log('远程命令输出:\n' + data);
}).stderr.on('data', (data) => {
console.log('远程命令错误:\n' + data);
});
});
}).on('data', (data) => {
console.log('npm install 输出:\n' + data);
}).stderr.on('data', (data) => {
console.log('npm install 错误:\n' + data);
});
});
}).catch((err) => {
console.log('上传失败:' + err);
});
});
}).connect(sshConfig);
// 监听error事件
conn.on('error', (err) => {
console.error('SSH连接失败', err);
});
// 结束SSH连接
conn.on('end', () => {
console.log('SSH连接已断开');
});