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