When building a statically generated site (SSG site) using Next.js or any other SSG framework, such as Hugo, Gatsby, Jekyll or Nuxt you might find yourself wanting to host the generated static files on an FTP server. Be the reason your own solution or a client requirement, this article will show you how to do it. Our target is to build a script that will enable you to simply run a single npm script to build and deploy the project to a FTP server.
$ npm run deploy
What kind of content can you host?
Using this method, you can host any static sites, which consist of static HTML, JavaScript and CSS files, or other static assets. The static pages may be handwritten from scratch or generated with any SSG framework. Any dynamic content must be either fetched client-side or the site must be regenerated and redeployed when content changes.
In this article I will be using Next.js as an example. If you are not using Next.js, the article will point out those steps which you need to adapt to your own framework.
What is FTP?
You’re going to need an FTP server. FTP stands for File Transfer Protocol, which is essentially sort of like HTTP, but for transmitting files between computers. You can read more about FTP here. You can connect to a remote FTP server directly with a FTP client such as FileZilla or Commander One. Those provide a graphical user interface for manually uploading and downloading files. While you could by all means use an FTP client, our target is to write a script that allows uploading your entire project to the FTP server using one single command: npm run deploy
Step 1: Build your project
Let’s start off by initializing an empty deploy
script in our package.json
.
{
...
"scripts": {
...
"deploy": ""
},
...
}
The first step for our deployment script is to build the project to static files. Using Next.js we must run two commands: next build
and next export
. In the case of Next.js it will generate our exported static files into a /out
directory by default.
Let’s write those as the first instructions for our deployment script.
"scripts": {
"deploy": "next build && next export"
}
For other frameworks: Replace next build && next export
with any commands that builds your project to static files into some output directory, such as npm run build
. For projects where no build step is required (manually writing static files), this step can be omitted.
Step 2: Setup custom JavaScript script for deploying
Create a directory /scripts
where we’ll create a file: deploy.js
for deploying. (Note: if you encounter problems running the script, attempt changing the filetype to deploy.mjs
).
Let’s just initialise it with an empty async main function for now.
async function main() {
return 0;
}
main().then(code => process.exit(code));
This deployment script will handle all the rest of the deployment procedures after we have built the project so we can hook it up to the npm script to complete it as follows.
"scripts": {
"deploy": "next build && next export && node ./scripts/deploy.js"
}
Step 3: Setup a .env file with your FTP credentials
You’ll need credentials to connect into an FTP server. Let’s save the FTP host, port, username and password into our .env
file for later use. The port 21 is commonly used for FTP.
FTP_USER=your-username-here
FTP_PASS=your-password-here
FTP_HOST=your.hostname.here
FTP_PORT=21
Step 4: Flesh out the deployment script
Let’s start up by installing the dependencies required for the script by running
$ npm i -D dotenv ftp-deploy
And import those at the top of your script
import dotenv from "dotenv";
import FtpDeploy from "ftp-deploy";
dotenv.config({ path: "./.env" });
And let’s fill out the deployment script. Most of the heavy lifting will be done by ftp-deploy
. Feel free to customise any settings for the deployment to better suit your needs. This configuration is the one I’ve used for uploading all Next.js build files to the root of an FTP server.
import dotenv from "dotenv";
import FtpDeploy from "ftp-deploy";
dotenv.config({ path: "./.env" });
async function main() {
try {
// Replace "/out" with your build directory which contains all generated static files
const outDir = path.join(process.cwd(), "/out");
await new FtpDeploy().deploy({
user: process.env.FTP_USER, // Your credentials
password: process.env.FTP_PASS, // Your credentials
host: process.env.FTP_HOST, // Your credentials
port: process.env.FTP_PORT, // Your credentials
localRoot: outDir, // Location of build files in project
remoteRoot: "/", // Upload location on remote, replace with subfolder on FTP-server if required
include: ["*", "**/*"], // Upload all files from build folder
exclude: [], // Exclude no files
deleteRemote: false, // Set to true if you want to delete ALL FILES in the remote root before uploading
forcePasv: true // Use passive mode
})
console.log("Succesfully deployed site")
return 0;
} catch (e) {
console.error("An error occured during deployment:", e);
return 1;
}
}
main().then((code) => process.exit(code));
You’re done.
Now running npm run deploy
will build your project to static files and automatically upload those static files.
Potential issues
The client wants to update the site by themselves
The client may update some of the content used on the site, which is used at the build step. The site will then show old data and must be redeployed. One, quite manual way of getting over this issue is to enable the client to update the site by themselves using the same update script. It will take some time to properly set up, but may save a lot of time later.
- Install
git
,node
andnpm
on your clients computer. - Add them to the project’s GitHub repository as a collaborator (under settings, then collaborators).
- Update the deploy script to include
git pull
andnpm i
at the start so it looks like"deploy": "git pull && npm i && next build && next export && node ./scripts/deploy.js"
. This will ensure the client always has the latest version of the project and correct dependencies installed before deploying. - Clone the repository onto the client’s machine with
git clone https://github.com/username/reponame.git
. - Add the correct credentials to the
.env
file at the project. - Write any script file on the client’s computer to an accessible location, name it something clear, such as
Update Project.bat
. The script file must do two things: 1. navigate to the project root and 2. runnpm run deploy
.
After this setup your client may freely update any data related to the site, for example on a headless CMS and after updating they can then click Update Project.bat
which will (from their point of view) run some magical code and the site is updated. Now all you need to do is ensure that the client doesn’t mess with any of the files in the project directory to make your life easier and only ever to interact with the project by running the update script.
Alternatively, you could provision a cloud computer to do the same thing when invoked via a webhook (that is triggered by any update to your client’s data sources) to automatically update the site without any client interaction. That however would cost you more than the previous $0 alternative.
Old unused files are left on the FTP server
If there are no other files on the FTP server at your remoteRoot
, you can use the deleteRemote: true
option to wipe all previous files when deploying new files. If you are unable to do that, you must either write custom logic to the script to delete previous files, manually delete unused files periodically or leave them be.
Top comments (1)
What about env variables and I can't see images which I import