How do I host static files on AWS S3 using next? How do I host static files on AWS S3 using next? heroku heroku

How do I host static files on AWS S3 using next?


next.config.js, set assetPrefix to your aws s3 link in production.

const isDev = process.env.NODE_ENV !== 'production';const version = require('./package.json').version;assetPrefix: isDev ? '' : `https://${process.env.AWS_REGION}.amazonaws.com/${process.env.AWS_S3_BUCKET_NAME}/${version}`,

collectstatic.js, run on postbuild release.

require('dotenv').config();const fs = require('fs');const readDir = require('recursive-readdir');const path = require('path');const AWS = require('aws-sdk');const mime = require('mime-types');const version = require('./package.json').version;// You will run this script from your CI/Pipeline after build has completed.// It will read the content of the build directory and upload to S3 (live assets bucket)// Every deployment is immutable. Cache will be invalidated every time you deploy.AWS.config.update({  region: process.env.AWS_S3_REGION,  accessKeyId: process.env.AWS_S3_ACCESS_KEY,  secretAccessKey: process.env.AWS_S3_SECRET_KEY,  maxRetries: 3});// Retrive al the files path in the build directoryconst getDirectoryFilesRecursive = (dir, ignores = []) => {  return new Promise((resolve, reject) => {    readDir(dir, ignores, (err, files) => (err ? reject(err) : resolve(files)));  });};// The Key will look like this: _next/public/<buildid>/pages/index.js// the <buildid> is exposed by nextJS and it's unique per deployment.// See: https://nextjs.org/blog/next-7/#static-cdn-supportconst generateFileKey = (fileName, toReplace, replaced) => {  // I'm interested in only the last part of the file: '/some/path/.next/build-manifest.json',  const S3objectPath = fileName.split(toReplace)[1];  return version + replaced + S3objectPath;};const s3 = new AWS.S3();const uploadToS3 = async (fileArray, toReplace, replaced) => {  try {    fileArray.map(file => {      // Configuring parameters for S3 Object      const S3params = {        Bucket: process.env.AWS_S3_BUCKET_NAME,        Body: fs.createReadStream(file),        Key: generateFileKey(file, toReplace, replaced),        ACL: 'public-read',        ContentType: String(mime.lookup(file)),        ContentEncoding: 'utf-8',        CacheControl: 'immutable,max-age=31536000,public'      };      s3.upload(S3params, function(err, data) {        if (err) {          // Set the exit code while letting          // the process exit gracefully.          console.error(err);          process.exitCode = 1;        } else {          console.log(`Assets uploaded to S3:`, data.key);        }      });    });  } catch (error) {    console.error(error);  }};// Start function// getDirectoryFilesRecursive(path, ignore);const start = async function(dict) {  for (var i = 0; i < dict.length; i++) {    const files = await getDirectoryFilesRecursive(path.resolve(__dirname, dict[i].filePath), ['.DS_Store', 'BUILD_ID']);    uploadToS3(files, dict[i].toReplace, dict[i].replaced);  }}// Call startstart([  {    filePath: '.next',    toReplace: '.next/',    replaced: '/_next/'  }]);

Run node collectstatic.js to upload all your assets to S3.


What is your source for Heroku has "a policy of not keeping those static files"?

It is true that if you want to add functionality of image uploads to your app, then a solution like S3 could be helpful, seeing as Heroku uses dynos (Isolated Linux Processes) that don't allow for dynamically writing to the filesystem.

Other than that use case ("users should be able to upload files"), using S3 for static files seems like an unnecessary complexity.

The NodeJS API for serving static files is:

app.use(express.static(path.join(__dirname, 'build')));

To experiment with this API on Heroku, I would deploy this barebones sample NodeJS/Express/React App using NodeJS's static files API.

The repo uses npm's react-scripts library to bundle and compile the React code, and the server simply serves the files bundled into the dynamically generated '/build' folder.

Hence your server code becomes as simple as:

const express = require('express');const http = require('http');const path = require('path');let app = express();app.use(express.static(path.join(__dirname, 'build')));const port = process.env.PORT || '8080';app.set('port', port);const server = http.createServer(app);server.listen(port);

If you really want S3 running with Node and Express I would checkout:This StackOverflow Thread.