forked from sheetjs/docs.sheetjs.com
		
	
		
			
	
	
		
			328 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			328 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						||
| 
								 | 
							
								title: NextJS
							 | 
						||
| 
								 | 
							
								pagination_prev: demos/extensions/index
							 | 
						||
| 
								 | 
							
								pagination_next: demos/gsheet
							 | 
						||
| 
								 | 
							
								---
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::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
							 | 
						||
| 
								 | 
							
								  <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:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```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:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.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.
							 |