| 
									
										
										
										
											2023-01-15 03:36:13 +00:00
										 |  |  | --- | 
					
						
							|  |  |  | title: NextJS | 
					
						
							| 
									
										
										
										
											2023-02-28 11:40:44 +00:00
										 |  |  | pagination_prev: demos/net/index | 
					
						
							|  |  |  | pagination_next: demos/mobile/index | 
					
						
							| 
									
										
										
										
											2023-01-15 03:36:13 +00:00
										 |  |  | --- | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-27 09:12:19 +00:00
										 |  |  | import current from '/version.js'; | 
					
						
							| 
									
										
										
										
											2023-04-29 11:21:37 +00:00
										 |  |  | import CodeBlock from '@theme/CodeBlock'; | 
					
						
							| 
									
										
										
										
											2023-04-27 09:12:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-15 03:36:13 +00:00
										 |  |  | :::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: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-29 11:21:37 +00:00
										 |  |  | <CodeBlock language="bash">{`\ | 
					
						
							|  |  |  | npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.1.1`} | 
					
						
							|  |  |  | </CodeBlock> | 
					
						
							| 
									
										
										
										
											2023-01-15 03:36:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-03 03:40:40 +00:00
										 |  |  | - `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) | 
					
						
							| 
									
										
										
										
											2023-01-15 03:36:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | The individual worksheets are available at | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-03 03:40:40 +00:00
										 |  |  | - `http://localhost:3000/sheets/0` | 
					
						
							|  |  |  | - `http://localhost:3000/sheets/1` | 
					
						
							|  |  |  | - `http://localhost:3000/sheets/2` | 
					
						
							| 
									
										
										
										
											2023-01-15 03:36:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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. |