---
title: Content and Site Generation
---
With the advent of server-side frameworks and content management systems, it is
possible to build sites whose source of truth is a spreadsheet!  This demo
explores a number of approaches.
## Lume
The official [Sheets plugin](https://lume.land/plugins/sheets/) uses SheetJS
to load data from spreadsheets.
#### Lume Demo
:::note
This was tested against `lume v1.12.0` on 2022 October 4.
:::
Complete Example (click to show)
1) Create a stock site:
```bash
mkdir -p sheetjs-lume
cd sheetjs-lume
deno run -A https://deno.land/x/lume/init.ts
```
When prompted, enter the following options:
- `Use TypeScript for the configuration file`: press Enter (use default `N`)
- `Do you want to use plugins`: type `sheets` and press Enter
The project will be configured and modules will be installed.
2) Download  and place in a `_data` folder:
```bash
mkdir -p _data
curl -LO https://sheetjs.com/pres.numbers
mv pres.numbers _data
```
3) Create a `index.njk` file that references the file.  Since the file is
   `pres.numbers`, the parameter name is `pres`:
```liquid title="index.njk"
Presidents
| Name | Index{% for row in pres %}{% if (loop.index >= 1) %} | 
      | {{ row.Name }} | {{ row.Index }} | 
  {% endif %}{% endfor %}
  
```
4) Run the development server:
```bash
deno task lume --serve
```
To verify it works, access http://localhost:3000 from your web browser.
Adding a new row and saving `pres.numbers` should refresh the data
5) Stop the server (press `CTRL+C` in the terminal window) and run
```bash
deno task lume
```
This will create a static site in the `_site` folder, which can be served with:
```bash
npx http-server _site
```
Accessing the page http://localhost:8080 will show the page contents.
${data.map(row => JSON.stringify(row)).join("\n")}
Base64 plugin (click to show)
This loader pulls in data as a Base64 string that can be read with `XLSX.read`.
While this approach works, it is not recommended since it loads the library in
the front-end site.
```js title="vite.config.js"
import { readFileSync } from 'fs';
import { defineConfig } from 'vite';
export default defineConfig({
  assetsInclude: ['**/*.xlsx'], // mark that xlsx file should be treated as assets
  plugins: [
    { // this plugin handles ?b64 tags
      name: "vite-b64-plugin",
      transform(code, id) {
        if(!id.match(/\?b64$/)) return;
        var path = id.replace(/\?b64/, "");
        var data = readFileSync(path, "base64");
        return `export default '${data}'`;
      }
    }
  ]
});
```
When importing using the `b64` query, the raw Base64 string will be exposed.
This can be read directly with `XLSX.read` in JS code:
```js title="main.js"
import { read, utils } from "xlsx";
/* reference workbook */
import b64 from './data.xlsx?b64';
/* parse workbook and export first sheet to CSV */
const wb = await read(b64);
const wsname = wb.SheetNames[0];
const csv = utils.sheet_to_csv(wb.Sheets[wsname]);
document.querySelector('#app').innerHTML = ``;
```
Complete Example (click to show)
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` 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
```
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 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` subfolder, which can be hosted with
```bash
npx http-server out
```
The command will start a local HTTP server on port 8080.
    
      
      
        
        | {{ row.Name }} | {{ row.Index }} | 
    
   
```
### Nuxt Content Demo
Complete Example (click to show)
:::note
This was tested against `create-nuxt-app v4.0.0` on 2022 November 07.
The generated project used Nuxt `v2.15.8` and Nuxt Content `v1.15.1`.
:::
1) Create a stock app:
```bash
npx create-nuxt-app@4.0.0 SheetJSNuxt
```
When prompted, enter the following options:
- `Project name`: press Enter (use default `SheetJSNuxt`)
- `Programming language`: press Down Arrow (`TypeScript` selected) then Enter
- `Package manager`: select `Npm` and press Enter
- `UI framework`: select `None` and press Enter
- `Nuxt.js modules`: scroll to `Content`, select with Space, then press Enter
- `Linting tools`: press Enter (do not select any Linting tools)
- `Testing framework`: select `None` and press Enter
- `Rendering mode`: select `Universal (SSR / SSG)` and press Enter
- `Deployment target`: select `Static (Static/Jamstack hosting)` and press Enter
- `Development tools`: press Enter (do not select any Development tools)
- `What is your GitHub username?`: press Enter
- `Version control system`: select `None`
The project will be configured and modules will be installed.
2) Install the SheetJS library and start the server:
```bash
cd SheetJSNuxt
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz
npm run dev
```
When the build finishes, the terminal will display a URL like:
```
ℹ Listening on: http://localhost:64688/
```
The server is listening on that URL.  Open the link in a web browser.
3) Download  and move to the `content` folder.
```bash
curl -LO https://sheetjs.com/pres.xlsx
mv pres.xlsx content/
```
4) Modify `nuxt.config.js` as described [earlier](#nuxtconfigjs-configuration)
5) Replace `pages/index.vue` with the following:
```html
  
    {{ item.name }}
    | Name | Index | 
|---|
      
        | {{ row.Name }} | {{ row.Index }} | 
    
   
 
```
The browser should refresh to show the contents of the spreadsheet.  If it does
not, click Refresh manually or open a new browser window.

6) To verify that hot loading works, open `pres.xlsx` from the `content` folder
in Excel.  Add a new row to the bottom and save the file:

The server terminal window should show a line like:
```
ℹ Updated ./content/pres.xlsx                                       @nuxt/content 05:43:37
```
The page should automatically refresh with the new content:

7) Stop the server (press `CTRL+C` in the terminal window) and run
```bash
npm run generate
```
This will create a static site in the `dist` folder, which can be served with:
```bash
npx http-server dist
```
Accessing the page http://localhost:8080 will show the page contents. Verifying
the static nature is trivial: make another change in Excel and save.  The page
will not change.