8.6 KiB
title | pagination_prev | pagination_next |
---|---|---|
NextJS | demos/net/index | 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:
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
- Disable NextJS telemetry:
npx next@13.1.1 telemetry disable
Confirm it is disabled by running
npx next@13.1.1 telemetry status
- Set up folder structure. At the end, a
pages
folder with asheets
subfolder must be created. On Linux or MacOS or WSL:
mkdir -p pages/sheets/
- 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
- Install dependencies:
{\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.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 ../..
- Test the deployment:
npx next@13.1.1
Open a web browser and access:
http://localhost:3000
landing pagehttp://localhost:3000/getStaticProps
shows data from the first sheethttp://localhost:3000/getServerSideProps
shows data from the first sheethttp://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
- 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.
- 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.
:::
- 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
- 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.