From FileZilla frustration to smooth, automated deployments — this is a full walkthrough of how I connected GitHub Actions to my cPanel hosting to deploy a Next.js site via FTP. Along the way, I hit some common roadblocks, and I’ll show you how I solved them.
💡 The Problem: Manual Deployments Were Driving Me Nuts
As a web developer managing multiple sites, I’ve always preferred fast, reliable workflows. But when working with clients who host on shared hosting with cPanel, deployment always ended up like this:
- Build the site locally
- Open FileZilla
- Drag and drop the build folder into
public_html
- Hope nothing breaks
That workflow might work for basic static HTML, but I was building modern sites with React and later Next.js. And I knew I needed something more reliable.
So I asked myself:
„Can I connect my GitHub repo directly to cPanel and let it auto-deploy via FTP?“
Turns out — yes. But it took some debugging.
🔧 Step 1: Choose the Deployment Approach
After researching a few methods, I settled on:
✅ GitHub Actions + FTP (via SamKirkland’s FTP-Deploy-Action)
This combo works well if:
- You’re on shared hosting (no SSH/SFTP access)
- You want to deploy from
main
branch - You can generate a static site (like from Create React App or exported Next.js)
🏗️ Step 2: Setting Up the Next.js App for Static Hosting
This part depends heavily on whether you’re using React (CRA) or Next.js.
🟢 If You’re Using Create React App (CRA)
You’re in luck. Just run:
npm run build
It creates a build/
folder — this is what you’ll upload via FTP.
🔵 If You’re Using Next.js (like me)
Here’s where things get tricky. Next.js doesn’t generate static files by default — it expects a Node.js server to render pages.
To make it work on cPanel (which doesn’t run Node.js), you must export the app statically:
Step-by-step:
- Edit
next.config.js
:
const nextConfig = {
output: 'export',
trailingSlash: true,
};
module.exports = nextConfig;
- In
package.json
, update your build script:
"build": "next build && next-sitemap"
❌ Don’t use
next export
directly. As of Next 15,next export
is deprecated when using App Router.
- Now running
npm run build
will generate anout/
folder — the folder we’ll deploy via FTP.
🐛 Issue I Faced
Initially, I used next export
like I did in older Next.js projects. That caused this error:
Error: `next export` has been removed in favor of output: 'export' in next.config.js
✅ Solution: Just define output: 'export'
and run next build
.
🔐 Step 3: Set Up Secrets in GitHub
Go to your repo > Settings > Secrets > Actions > New Secret:
You’ll need these:
-
FTP_SERVER
→ Use your server IP (not the domain, unless it resolves correctly) -
FTP_USERNAME
→ FTP user (e.g.,deployment@yourdomain.com
) -
FTP_PASSWORD
→ FTP password -
FTP_SERVER_DIR
→ The path where you want files deployed
🐛 Issue I Faced
I initially set FTP_SERVER
to ftp.afrojazzfestivalnigeria.com
, but got this error:
getaddrinfo ENOTFOUND ftp.afrojazzfestivalnigeria.com
✅ Solution: Use your server IP address instead.
Another gotcha:
Error: server-dir should be a folder (must end with /)
✅ Solution: Always make sure FTP_SERVER_DIR
ends with a /
, e.g. /public_html/
Later, I found out my FTP account was already scoped to the addon domain folder, so I could just use:
FTP_SERVER_DIR=/
⚙️ Step 4: GitHub Actions Workflow File
Here’s my final working .github/workflows/deploy.yml
:
name: 🚀 Deploy to cPanel via FTP
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v3
- name: 🧱 Install Dependencies
run: npm install
- name: 🛠️ Build App
run: npm run build
- name: 📤 Deploy via FTP
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
server-dir: ${{ secrets.FTP_SERVER_DIR }}
local-dir: out/
🧪 Final Test — and It Worked!
After pushing to main
, I watched the GitHub Actions tab light up:
- ✅ Dependencies installed
- ✅ App built to
out/
- ✅ FTP deploy complete
Logged into cPanel — the files were there. Opened the domain in my browser — live site.
🧠 Lessons Learned
-
Next.js requires **
output: 'export'
**** for static builds** -
You cannot use **
"use client"
**** with *generateStaticParams()
* in the same file** — split client/server components - Use your IP instead of
ftp.domain.com
if DNS isn’t resolving - Always end your
server-dir
with a/
- Don’t overthink it — once it works, you can reuse it everywhere
💬 Closing Thoughts
This whole process turned a frustrating manual task into a smooth developer experience. Now I just push to GitHub and let Actions do the rest.
I hope this guide saves you a few hours of trial and error.
If it helped, leave a ⭐ on SamKirkland’s action and let me know how yours went!
🔗 Want to see the site? Visit afrojazzfestivalnigeria.com
📬 Got questions? Reach out on LinkedIn or leave a comment on my post!