I've been enjoying building this site - a static site with Umbraco and Astro - and then copying and pasting the files onto a server with Filezilla (as God intended).
But I decided that maybe there was a better way.
For this small personal project I didn't want to use a standard CI/CD pipeline, I wanted to have a specific "publish" command instead of automatically pushing new content. That being said, this approach can very easily be migrated to a GitHub Action, and for larger projects with more contributors I would recommend that approach.
The most blunt consideration I had was to just delete everything on the server every time and replace with the new files. I didn't want to just replace the updated files since that could lead to orphaned pages etc.
I ended up coming across ftp-deploy which perfectly handles the deploy task, while also creating a state file - .ftp-deploy-sync-state.json - so that future deployments only adjust what is already online.
I ended up putting together a deploy.js module to run this process. You should be able to just run it from the CLI, as part of a package.json defined actino but I ran into problems reading environment variables.
import { deploy } from '@samkirkland/ftp-deploy';import { config } from 'dotenv';import { fileURLToPath } from 'url';import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);const __dirname = dirname(__filename);
// Load .env.localconfig({ path: resolve(__dirname, '.env.local') });
const isDryRun = process.argv.includes('--dry-run');
try { await deploy({ server: process.env.FTP_SERVER, username: process.env.FTP_USER, password: process.env.FTP_PASS, "local-dir": './dist/', "server-dir": './public_html/', protocol: 'ftps', logLevel: 'verbose', dryRun: isDryRun });
console.log('✅ Deployment completed successfully!');} catch (error) { console.error('❌ Deployment failed:', error.message); process.exit(1);}So now I have a script for pushing static files to a server with minimal server usage!
One more set of clicks removed from my workflow :)