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. |