Skip to main content

NextJS

note

This was tested against next v13.1.1 on 2023 January 14.

info

At a high level, there are two ways to pull spreadsheet data into NextJS apps: loading an asset module or performing the file read operations from the NextJS lifecycle. At the time of writing, NextJS does not offer an out-of-the-box asset module solution, so this demo focuses on raw operations. NextJS does not watch the spreadsheets, so next dev hot reloading will not work!

The general strategy with NextJS apps is to generate HTML snippets or data from the lifecycle functions and reference them in the template.

HTML output can be generated using XLSX.utils.sheet_to_html and inserted into the document using the dangerouslySetInnerHTML attribute:

export default function Index({html, type}) { return (
// ...
<div dangerouslySetInnerHTML={{ __html: html }} />
// ...
); }
Reading and writing files during the build process

fs cannot be statically imported from the top level in NextJS pages. The dynamic import must happen within a lifecycle function. For example:

/* it is safe to import the library from the top level */
import { readFile, utils, set_fs } from 'xlsx';
/* it is not safe to import 'fs' from the top level ! */
// import * as fs from 'fs'; // this will fail
import { join } from 'path';
import { cwd } from 'process';

export async function getServerSideProps() {
set_fs(await import("fs")); // dynamically import 'fs' when needed
const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")); // works
// ...
}
Next 13+ and SWC

Next 13 switched to the SWC minifier. There are known issues with the minifier. Until those issues are resolved, SWC should be disabled in next.config.js:

next.config.js
module.exports = {
swcMinify: false
};

NextJS Strategies

NextJS currently provides 3 strategies:

  • "Static Site Generation" using getStaticProps
  • "SSG with Dynamic Routes" using getStaticPaths
  • "Server-Side Rendering" using getServerSideProps

Static Site Generation

When using getStaticProps, the file will be read once during build time.

import { readFile, set_fs, utils } from 'xlsx';

export async function getStaticProps() {
/* read file */
set_fs(await import("fs"));
const wb = readFile(path_to_file)

/* generate and return the html from the first worksheet */
const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
return { props: { html } };
};

Dynamic Routes

Typically a static site with dynamic routes has an endpoint /sheets/[id] that implements both getStaticPaths and getStaticProps.

  • getStaticPaths should return an array of worksheet indices:
export async function getStaticPaths() {
/* read file */
set_fs(await import("fs"));
const wb = readFile(path);

/* generate an array of objects that will be used for generating pages */
const paths = wb.SheetNames.map((name, idx) => ({ params: { id: idx.toString() } }));
return { paths, fallback: false };
};
note

For a pure static site, fallback must be set to false!

  • getStaticProps will generate the actual HTML for each page:
export async function getStaticProps(ctx) {
/* read file */
set_fs(await import("fs"));
const wb = readFile(path);

/* get the corresponding worksheet and generate HTML */
const ws = wb.Sheets[wb.SheetNames[ctx.params.id]]; // id from getStaticPaths
const html = utils.sheet_to_html(ws);
return { props: { html } };
};

Server-Side Rendering

Do not use on a static site

These routes require a NodeJS dynamic server. Static page generation will fail!

getStaticProps and getStaticPaths support static site generation (SSG).

getServerSideProps is suited for NodeJS hosted deployments where the workbook changes frequently and a static site is undesirable.

When using getServerSideProps, the file will be read on each request.

import { readFile, set_fs, utils } from 'xlsx';

export async function getServerSideProps() {
/* read file */
set_fs(await import("fs"));
const wb = readFile(path_to_file);

/* generate and return the html from the first worksheet */
const html = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]);
return { props: { html } };
};

Demo

0) Disable NextJS telemetry:

npx [email protected]13.1.1 telemetry disable

Confirm it is disabled by running

npx [email protected]13.1.1 telemetry status

1) Set up folder structure. At the end, a pages folder with a sheets subfolder must be created. On Linux or MacOS or WSL:

mkdir -p pages/sheets/

2) Download the test file and place in the project root. On Linux or MacOS or WSL:

curl -LO https://docs.sheetjs.com/next/sheetjs.xlsx

3) Install dependencies:

npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz [email protected]

4) Download test scripts:

Download and place the following scripts in the pages subfolder:

Download [id].js and place in the pages/sheets subfolder.

Percent-Encoding in the script name

The [id].js script must have the literal square brackets in the name. If your browser saved the file to %5Bid%5D.js. rename the file.

On Linux or MacOS or WSL:

cd pages
curl -LO https://docs.sheetjs.com/next/index.js
curl -LO https://docs.sheetjs.com/next/getServerSideProps.js
curl -LO https://docs.sheetjs.com/next/getStaticPaths.js
curl -LO https://docs.sheetjs.com/next/getStaticProps.js
cd sheets
curl -LOg 'https://docs.sheetjs.com/next/[id].js'
cd ../..

5) Test the deployment:

Open a web browser and access:

  • http://localhost:3000 landing page
  • http://localhost:3000/getStaticProps shows data from the first sheet
  • http://localhost:3000/getServerSideProps shows data from the first sheet
  • http://localhost:3000/getStaticPaths shows a list (3 sheets)

The individual worksheets are available at

  • http://localhost:3000/sheets/0
  • http://localhost:3000/sheets/1
  • http://localhost:3000/sheets/2

6) Stop the server and run a production build:

The final output will show a list of the routes and types:

Route (pages)                              Size     First Load JS
┌ ○ / 541 B 77.4 kB
├ ○ /404 181 B 73.7 kB
├ λ /getServerSideProps 594 B 77.4 kB
├ ● /getStaticPaths 2.56 kB 79.4 kB
├ ● /getStaticProps 591 B 77.4 kB
└ ● /sheets/[id] (447 ms) 569 B 77.4 kB
├ /sheets/0
├ /sheets/1
└ /sheets/2

As explained in the summary, the /getStaticPaths and /getStaticProps routes are completely static. 3 /sheets/# pages were generated, corresponding to 3 worksheets in the file. /getServerSideProps is server-rendered.

7) Try to build a static site:

The static export will fail!

A static page cannot be generated at this point because /getServerSideProps is still server-rendered.

8) Delete pages/getServerSideProps.js and rebuild:

rm -f pages/getServerSideProps.js
npx [email protected] build

Inspecting the output, there should be no lines with the λ symbol:

Route (pages)                              Size     First Load JS
┌ ○ / 541 B 77.4 kB
├ ○ /404 181 B 73.7 kB
├ ● /getStaticPaths 2.56 kB 79.4 kB
├ ● /getStaticProps 591 B 77.4 kB
└ ● /sheets/[id] (459 ms) 569 B 77.4 kB
├ /sheets/0
├ /sheets/1
└ /sheets/2

9) Generate the static site:

The static site will be written to the out subfolder, which can be hosted with

npx http-server out

The command will start a local HTTP server for testing the generated site. Note that /getServerSideProps will 404 since the page was removed.