--- title: NextJS pagination_prev: demos/net/index pagination_next: demos/mobile/index --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; :::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: ```jsx export default function Index({html, type}) { return ( // ... // highlight-next-line
// ... ); } ``` :::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: ```js /* 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`: ```js title="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. ```js 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: ```js 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: ```js 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. ```js 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: ```js npx next@13.1.1 telemetry disable ``` Confirm it is disabled by running ```js 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: ```bash mkdir -p pages/sheets/ ``` 2) Download the [test file](pathname:///next/sheetjs.xlsx) and place in the project root. On Linux or MacOS or WSL: ```bash curl -LO https://docs.sheetjs.com/next/sheetjs.xlsx ``` 3) Install dependencies: {`\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.1.1`} 4) Download test scripts: Download and place the following scripts in the `pages` subfolder: - [`index.js`](pathname:///next/index.js) - [`getServerSideProps.js`](pathname:///next/getServerSideProps.js) - [`getStaticPaths.js`](pathname:///next/getStaticPaths.js) - [`getStaticProps.js`](pathname:///next/getStaticProps.js) Download [`[id].js`](pathname:///next/%5Bid%5D.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: ```bash 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: ```bash npx next@13.1.1 ``` 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: ```bash 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. 7) Try to build a static site: ```bash 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. ::: 8) Delete `pages/getServerSideProps.js` and rebuild: ```bash 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 ``` 9) Generate the static site: ```bash npx next@13.1.1 export ``` The static site will be written to the `out` subfolder, which can be hosted with ```bash 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.