forked from sheetjs/docs.sheetjs.com
		
	ssg
This commit is contained in:
		
							parent
							
								
									d5b838993d
								
							
						
					
					
						commit
						99dd5c8834
					
				| @ -21,6 +21,310 @@ in the parsing logic, issues should then be raised with the SheetJS project. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## NextJS | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This was tested against `next v12.2.5` on 2022 August 16. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::caution | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| `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 | ||||
|   // ... | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Strategies | ||||
| 
 | ||||
| #### "Static Site Generation" using `getStaticProps` | ||||
| 
 | ||||
| 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 } }; | ||||
| }; | ||||
| ``` | ||||
| 
 | ||||
| #### "Static Site Generation with Dynamic Routes" using `getStaticPaths` | ||||
| 
 | ||||
| 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" using `getServerSideProps` | ||||
| 
 | ||||
| :::caution Do not use on a static site | ||||
| 
 | ||||
| These routes require a NodeJS dynamic server. Static page generation will fail! | ||||
| 
 | ||||
| `getStaticProps` and `getStaticPaths` support 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 | ||||
| 
 | ||||
| <details open><summary><b>Complete Example</b> (click to show)</summary> | ||||
| 
 | ||||
| 0) Disable NextJS telemetry: | ||||
| 
 | ||||
| ```js | ||||
| npx next telemetry disable | ||||
| ``` | ||||
| 
 | ||||
| Confirm it is disabled by running | ||||
| 
 | ||||
| ```js | ||||
| npx next 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 | ||||
| ``` | ||||
| 
 | ||||
| 4) Download test scripts: | ||||
| 
 | ||||
| Download and place the following scripts in the `pages` subdirectory: | ||||
| 
 | ||||
| - [`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` subdirectory. | ||||
| 
 | ||||
| :::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 | ||||
| ``` | ||||
| 
 | ||||
| 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 dev server and run a production build: | ||||
| 
 | ||||
| ```bash | ||||
| npx next build | ||||
| ``` | ||||
| 
 | ||||
| The final output will show a list of the routes and types: | ||||
| 
 | ||||
| ``` | ||||
| Route (pages)                              Size     First Load JS | ||||
| ┌ ○ /                                      551 B          81.7 kB | ||||
| ├ ○ /404                                   194 B          77.2 kB | ||||
| ├ λ /getServerSideProps                    602 B          81.7 kB | ||||
| ├ ● /getStaticPaths                        2.7 kB         83.8 kB | ||||
| ├ ● /getStaticProps                        600 B          81.7 kB | ||||
| └ ● /sheets/[id] (312 ms)                  580 B          81.7 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 export | ||||
| ``` | ||||
| 
 | ||||
| :::note The static export will fail! | ||||
| 
 | ||||
| A static page cannot be generated at this point because `/getServerSideProps` | ||||
| is still server-rendered. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 8) Remove `pages/getServerSideProps.js` and rebuild with `npx next build`. | ||||
| 
 | ||||
| Inspecting the output, there should be no lines with the `λ` symbol: | ||||
| 
 | ||||
| ``` | ||||
| Route (pages)                              Size     First Load JS | ||||
| ┌ ○ /                                      551 B          81.7 kB | ||||
| ├ ○ /404                                   194 B          77.2 kB | ||||
| ├ ● /getStaticPaths                        2.7 kB         83.8 kB | ||||
| ├ ● /getStaticProps                        600 B          81.7 kB | ||||
| └ ● /sheets/[id] (312 ms)                  580 B          81.7 kB | ||||
|     ├ /sheets/0 | ||||
|     ├ /sheets/1 | ||||
|     └ /sheets/2 | ||||
| ``` | ||||
| 
 | ||||
| 9) Generate the static site: | ||||
| 
 | ||||
| ```bash | ||||
| npx next export | ||||
| ``` | ||||
| 
 | ||||
| The static site will be written to the `out` subdirectory, which can be hosted with | ||||
| 
 | ||||
| ```bash | ||||
| npx http-server out | ||||
| ``` | ||||
| 
 | ||||
| The command will start a local webserver on port 8080. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| ## NuxtJS | ||||
| 
 | ||||
| `@nuxt/content` is a file-based CMS for Nuxt, enabling static-site generation | ||||
| @ -99,7 +403,7 @@ neatly with nested `v-for`: | ||||
| 
 | ||||
| ### Nuxt Content Demo | ||||
| 
 | ||||
| <details open><summary><b>Complete Example</b> (click to show)</summary> | ||||
| <details><summary><b>Complete Example</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										47
									
								
								docz/static/next/[id].js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										47
									
								
								docz/static/next/[id].js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| import Head from 'next/head'; | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export default function Index({type, html, name}) { return ( <div> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     <b>{name}</b> | ||||
|     <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   </pre> | ||||
| </div> ); } | ||||
| 
 | ||||
| let cache = []; | ||||
| 
 | ||||
| export async function getStaticProps(ctx) { | ||||
|   if(!cache || !cache.length) { | ||||
|     set_fs(await import("fs")); | ||||
|     const wb = readFile(join(cwd(), "sheetjs.xlsx")); | ||||
|     cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); | ||||
|   } | ||||
|   const entry = cache[ctx.params.id]; | ||||
|   return { | ||||
|     props: { | ||||
|       type: "getStaticPaths", | ||||
|       name: entry.name, | ||||
|       id: ctx.params.id.toString(), | ||||
|       html: entry.sheet ? utils.sheet_to_html(entry.sheet) : "", | ||||
|     }, | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export async function getStaticPaths() { | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(join(cwd(), "sheetjs.xlsx")); | ||||
|   cache = wb.SheetNames.map((name) => ({ name, sheet: wb.Sheets[name] })); | ||||
|   return { | ||||
|     paths: wb.SheetNames.map((name, idx) => ({ params: { id: idx.toString()  } })), | ||||
|     fallback: false, | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										29
									
								
								docz/static/next/getServerSideProps.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										29
									
								
								docz/static/next/getServerSideProps.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| import Head from 'next/head'; | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export default function Index({type, html}) { return ( <div> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     It generates HTML from the first sheet.<br/><br/> | ||||
|     <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   </pre> | ||||
| </div> ); } | ||||
| 
 | ||||
| export async function getServerSideProps() { | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(join(cwd(), "sheetjs.xlsx")) | ||||
|   return { | ||||
|     props: { | ||||
|       type: "getServerSideProps", | ||||
|       html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]), | ||||
|     }, | ||||
|   } | ||||
| } | ||||
							
								
								
									
										34
									
								
								docz/static/next/getStaticPaths.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										34
									
								
								docz/static/next/getStaticPaths.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| import Head from 'next/head'; | ||||
| import Link from "next/link"; | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export default function Index({type, snames}) { return ( <div> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     Each worksheet maps to a path:<br/><br/> | ||||
|     <ul> | ||||
| {snames.map((sname, idx) => (<li key={idx}> | ||||
|   <Link href="/sheets/[id]" as={`/sheets/${idx}`}><a>{`Sheet index=${idx} name="${sname}"`}</a></Link> | ||||
| </li>))} | ||||
|     </ul> | ||||
|   </pre> | ||||
| </div> ); } | ||||
| 
 | ||||
| export async function getStaticProps() { | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(join(cwd(), "sheetjs.xlsx")) | ||||
|   return { | ||||
|     props: { | ||||
|       type: "getStaticPaths", | ||||
|       snames: wb.SheetNames, | ||||
|     }, | ||||
|   } | ||||
| } | ||||
							
								
								
									
										29
									
								
								docz/static/next/getStaticProps.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										29
									
								
								docz/static/next/getStaticProps.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| import Head from 'next/head'; | ||||
| import { readFile, set_fs, utils } from 'xlsx'; | ||||
| import { join } from 'path'; | ||||
| import { cwd } from 'process'; | ||||
| 
 | ||||
| export default function Index({type, html}) { return ( <div> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>{`SheetJS Next.JS ${type} Demo`}</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>{`SheetJS Next.JS ${type} Demo`}</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     It generates HTML from the first sheet.<br/><br/> | ||||
|     <div dangerouslySetInnerHTML={{ __html: html }} /> | ||||
|   </pre> | ||||
| </div> ); } | ||||
| 
 | ||||
| export async function getStaticProps() { | ||||
|   set_fs(await import("fs")); | ||||
|   const wb = readFile(join(cwd(), "sheetjs.xlsx")) | ||||
|   return { | ||||
|     props: { | ||||
|       type: "getStaticProps", | ||||
|       html: utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]), | ||||
|     }, | ||||
|   } | ||||
| } | ||||
							
								
								
									
										16
									
								
								docz/static/next/index.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										16
									
								
								docz/static/next/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| import Head from 'next/head'; | ||||
| 
 | ||||
| export default function Index() { return ( <div> | ||||
|   <Head> | ||||
|     <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <title>SheetJS Next.JS Demo</title> | ||||
|     <style jsx>{`body, #app { height: 100%; };`}</style> | ||||
|   </Head> | ||||
|   <pre> | ||||
|     <h3>SheetJS Next.JS Demo</h3> | ||||
|     This demo reads from /sheetjs.xlsx<br/><br/> | ||||
|     - <a href="/getStaticProps">getStaticProps</a><br/><br/> | ||||
|     - <a href="/getServerSideProps">getServerSideProps</a><br/><br/> | ||||
|     - <a href="/getStaticPaths">getStaticPaths</a><br/> | ||||
|   </pre> | ||||
| </div> ); } | ||||
							
								
								
									
										
											BIN
										
									
								
								docz/static/next/sheetjs.xlsx
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/next/sheetjs.xlsx
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user