Skip to content

Instantly share code, notes, and snippets.

@fgnass
Created October 19, 2020 14:04
Show Gist options
  • Save fgnass/e4d3435bf3face47a9deb9cd3a8ba21f to your computer and use it in GitHub Desktop.
Save fgnass/e4d3435bf3face47a9deb9cd3a8ba21f to your computer and use it in GitHub Desktop.
Next.js SSR + Capacitor

Goal

The goal is to package a server-side rendered Next.js app as SPA for capacitor.

Approach

Pages with dynamic routes/data use getServerSideProps(). For capacitor we need at least one page that can be rendered statically, preferably the index page.

When there are pages that use getServerSiedeProps() we can't use next export (it will fail with an error). Instead we use the export.js script to copy the static pages from /.next/server/pages to /www as well as /public, /.next/static is copied to /www/_next/static.

Upon client-side navigation Next.js tries to load the JSON data from /_next/data/${BUILD_ID}/.... This leads to a 404 as it resolves to https://localhost (or capacitor://) from where capacitor loaded the page.

Unfortunately neither Next's assetPrefix nor basePath settings help here.

As workaround we overwrite window.fetch when the app is served from a capacitor origin and rewrite all URLs that start with /_next/data or /_next/api:

if (typeof window !== "undefined" && (window.origin === "https://localhost" || window.origin === "capacitor://")) {
  const _fetch = window.fetch;
  window.fetch = (res, init) => {
    if (typeof res === "string" && (res.startsWith("/_next/data") || res.startsWith("/_next/api"))) {
      res = "http://example.com" + res;
    }
    return _fetch(res, init);
  };
}

Credits

The idea is based on James Hegedus' Gist for static asset hoisting for Firebase Hosting CDN: https://gist.github.com/jthegedus/8e820d37e1f3768f991886fb65de154f

var shell = require("shelljs");
var distDir = ".next";
var BUILD_ID = shell.cat(`${distDir}/BUILD_ID`);
function hoistPages(fileExt, outputPath) {
console.log(`${distDir}/server/pages/**/*${fileExt} -> ${outputPath}/`);
shell.mkdir("-p", outputPath);
var match = new RegExp("\\" + `${fileExt}`);
var filesToHoist = shell
.find(`${distDir}/server/pages/`)
.filter(function (file) {
// ensure the file has the required extension and is not a dynamic route (/blog/[pid])
return file.match(match) && file.match(/^((?!\[|\]).)*$/);
});
filesToHoist.forEach((filePath) => {
var outPath = filePath.split("pages/")[1];
if (outPath.includes("/")) {
shell.mkdir(
"-p",
`${outputPath}/${outPath.substring(0, outPath.lastIndexOf("/"))}`
);
}
shell.cp("-f", filePath, `${outputPath}/${outPath}`);
});
}
console.log(
"next export doesn't support getServerSideProps() so we perform our own copy of static assets to package it for capacitor"
);
console.log(
"Hoist public/ Next.js runtime and optimised chunks, computed .html and .json data\n"
);
shell.rm("-rf", "www/*");
console.log("public/ -> www/");
shell.mkdir("-p", "www/");
shell.cp("-Rf", "public/*", "www/");
console.log(`${distDir}/static/ -> www/_next/static/`);
shell.mkdir("-p", "www/_next/static/");
shell.cp("-Rf", `${distDir}/static/`, "www/_next/");
hoistPages(".html", "www");
hoistPages(".json", `www/_next/data/${BUILD_ID}`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment