docs.sheetjs.com/docz/docs/03-demos/11-static/08-nextjs.md
2023-02-17 21:33:30 -05:00

8.5 KiB

title pagination_prev pagination_next
NextJS demos/extensions/index demos/cli

:::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 (
  // ...
// highlight-next-line
  <div dangerouslySetInnerHTML={{ __html: html }} />
  // ...
); }

:::warning 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() {
// highlight-next-line
  set_fs(await import("fs")); // dynamically import 'fs' when needed
  const wb = readFile(join(cwd(), "public", "sheetjs.xlsx")); // works
  // ...
}

:::

:::caution 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:

module.exports = {
// highlight-next-line
  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

:::caution 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

  1. Disable NextJS telemetry:
npx next@13.1.1 telemetry disable

Confirm it is disabled by running

npx next@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/
  1. 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
  1. Install dependencies:
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz next@13.1.1
  1. Download test scripts:

Download and place the following scripts in the pages subfolder:

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

:::caution 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 ../..
  1. Test the deployment:
npx next@13.1.1

Open a web browser and access:

The individual worksheets are available at

  1. Stop the server and run a production build:
npx next@13.1.1 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.

  1. Try to build a static site:
npx next@13.1.1 export

:::note The static export will fail!

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

:::

  1. Delete pages/getServerSideProps.js and rebuild:
rm -f pages/getServerSideProps.js
npx next@13.1.1 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
  1. Generate the static site:
npx next@13.1.1 export

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.