---
title: NuxtJS
pagination_prev: demos/net/index
pagination_next: demos/mobile/index
---
import current from '/version.js';
import CodeBlock from '@theme/CodeBlock';
`@nuxt/content` is a file-based CMS for Nuxt, enabling static-site generation
and on-demand server rendering powered by spreadsheets.
The [NodeJS module](/docs/getting-started/installation/nodejs) can be imported
from Content v1 "parsers" and Content v2 "transformers".
:::note
The following deployments were tested:
| Nuxt Content | Nuxt     | Date       |
|:-------------|:---------|:-----------|
| `1.15.1`     | `2.16.3` | 2023-06-01 |
| `2.3.0`      | `3.0.0`  | 2023-01-19 |
| `2.6.0`      | `3.5.2`  | 2023-06-01 |
:::
:::caution Telemetry
Nuxt embeds telemetry. According to the developers, it is disabled by default.
To explicitly disable telemetry, the official documentation recommends:
```bash
npx nuxt telemetry disable
```
At the time the demo was last tested, this command did not work.  Instead, a
option should be added in `nuxt.config.ts` or `nuxt.config.js` for Nuxt 3 sites:
```js title="nuxt.config.js"
// ...
// highlight-start
export default defineNuxtConfig({
  // @ts-ignore
  telemetry: false,
// highlight-end
  // ...
})
```
A global setting can be added to `.nuxtrc` in the user home directory:
```ini title=".nuxtrc"
telemetry.enabled=false
```
:::
## Nuxt Content v1
Nuxt Content v1 is designed to work with Nuxt v2.
The following diagram depicts the workbook waltz:
```mermaid
flowchart LR
  file[(workbook\nfile)]
  subgraph SheetJS operations
    buffer(NodeJS\nBuffer)
    aoo(array of\nobjects)
  end
  html{{HTML\nTABLE}}
  file --> |nuxt.config.js\ncustom parser| buffer
  buffer --> |nuxt.config.js\ncustom parser| aoo
  aoo --> |index.vue\ntemplate| html
```
### Configuration
Through an override in `nuxt.config.js`, Nuxt Content will use custom parsers.
Differences from a stock `create-nuxt-app` config are shown below:
```js title="nuxt.config.js"
import { readFile, utils } from 'xlsx';
// This will be called when the files change
const parseSheet = (file, { path }) => {
  // `path` is a path that can be read with `XLSX.readFile`
  const wb = readFile(path);
  const o = wb.SheetNames.map(name => ({ name, data: utils.sheet_to_json(wb.Sheets[name])}));
  return { data: o };
};
export default {
// ...
  // content.extendParser allows us to hook into the parsing step
  content: {
    extendParser: {
      // the keys are the extensions that will be matched.  The "." is required
      ".numbers": parseSheet,
      ".xlsx": parseSheet,
      ".xls": parseSheet,
      // can add other extensions like ".fods" as desired
    }
  },
// ...
}
```
### Template Use
When a spreadsheet is placed in the `content` folder, Nuxt will find it.  The
data can be referenced in a view with `asyncData`.  The name should not include
the extension, so `"sheetjs.numbers"` would be referenced as `"sheetjs"`:
```js
  async asyncData ({$content}) {
    return {
      // $content('sheetjs') will match files with extensions in nuxt.config.js
      data: await $content('sheetjs').fetch()
    };
  }
```
In the template, `data.data` is an array of objects.  Each object has a `name`
property for the worksheet name and a `data` array of row objects.  This maps
neatly with nested `v-for`:
```xml
  
  
    
      
      
        
        | {{ row.Name }} | {{ row.Index }} | 
    
   
```
### Nuxt Content Demo
:::caution
When the demo was last tested, parts of the Nuxt dependency tree did not support
NodeJS version 20.  The creation step will show warnings like
```
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE   package: '@nuxt/types@2.16.3',
npm WARN EBADENGINE   required: { node: '^14.18.0 || ^16.10.0 || ^17.0.0 || ...
npm WARN EBADENGINE   current: { node: 'v20.2.0', npm: '9.6.6' }
npm WARN EBADENGINE }
```
The recommended solution is to switch to Node 18.
:::
1) Create a stock app:
```bash
npx create-nuxt-app@4.0.0 sheetjs-nuxt
```
When prompted, enter the following options:
- `Project name`: press Enter (use default `sheetjs-nuxt`)
- `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:
{`\
cd sheetjs-nuxt
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.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 -L -o content/pres.xlsx https://sheetjs.com/pres.xlsx
```
4) Modify `nuxt.config.js` as follows:
- Add the following to the top of the script:
```js
import { readFile, utils } from 'xlsx';
// This will be called when the files change
const parseSheet = (file, { path }) => {
  // `path` is a path that can be read with `XLSX.readFile`
  const wb = readFile(path);
  const o = wb.SheetNames.map(name => ({ name, data: utils.sheet_to_json(wb.Sheets[name])}));
  return { data: o };
};
```
- Look for the exported object.  There should be a `content` property:
```js
  // Content module configuration: https://go.nuxtjs.dev/config-content
  content: {},
```
Replace the property with the following definition:
```js
  // content.extendParser allows us to hook into the parsing step
  content: {
    extendParser: {
      // the keys are the extensions that will be matched.  The "." is required
      ".numbers": parseSheet,
      ".xlsx": parseSheet,
      ".xls": parseSheet,
      // can add other extensions like ".fods" as desired
    }
  },
```
(If the property is missing, add it to the end of the exported object)
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 live reload 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.
## Nuxt Content v2
Nuxt Content v2 is designed to work with Nuxt v3.
The following diagram depicts the workbook waltz:
```mermaid
flowchart LR
  file[(workbook\nfile)]
  subgraph SheetJS operations
    buffer(NodeJS\nBuffer)
    aoo(array of\nobjects)
  end
  html{{HTML\nTABLE}}
  file --> |custom module\ntransformer| buffer
  buffer --> |custom module\ntransformer| aoo
  aoo --> |index.vue\nContentRenderer| html
```
### Overview
Nuxt Content `v2` supports custom transformers for controlling data.  Although
the library hard-codes UTF-8 interpretations, the `_id` field currently uses
the pattern `content:` followed by the filename (if files are placed in the
`content` folder directly).  This enables a transformer to re-read the file:
```ts title="Transformer"
// @ts-ignore
import { defineTransformer } from "@nuxt/content/transformers/utils";
import { read, utils } from "xlsx";
import { readFileSync } from "node:fs";
import { resolve } from 'node:path';
export default defineTransformer({
  name: 'sheetformer',
  extensions: ['.xlsx'],
  parse (_id: string, rawContent: string) {
    // highlight-start
    /* read the underlying file */
    const buf = readFileSync(resolve("./content/" + _id.slice(8)));
    /* parse */
    const wb = read(buf);
    // highlight-end
    /* generate JS objects for each worksheet */
    const body = wb.SheetNames.map(name => ({ name, data: utils.sheet_to_json(wb.Sheets[name])}));
    return { _id, body };
  }
});
```
Pages can pull data using `useAsyncData`:
```html title="Page"
```
Pages should use `ContentRenderer` to reference the data:
```html title="Page"
  
  
    
    {{ item.name }}
    
    | Name | Index | 
|---|
      
        
        | {{ row.Name }} | {{ row.Index }} | 
    
   
```
### Nuxt Content 2 Demo
:::caution
When the demo was last tested, parts of the Nuxt dependency tree did not support
NodeJS version 20. If the `yarn install` step fails with a message like
```
error @nuxt/kit@3.4.1: The engine "node" is incompatible with this module.
```
The recommended solution is to switch to Node 18.
:::
1) Create a stock app and install dependencies:
```bash
npx -y nuxi init -t content sheetjs-nc2
cd sheetjs-nc2
npx -y yarn install
npx -y yarn add --dev @types/node
```
2) Install the SheetJS library and start the server:
{`\
npx -y yarn add https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz
npx -y yarn dev`}
When the build finishes, the terminal will display a URL like:
```
  > Local:    http://localhost:3000/
```
The server is listening on that URL.  Open the link in a web browser.
3) Download  and move to the `content` folder.
```bash
curl -L -o content/pres.xlsx https://sheetjs.com/pres.xlsx
```
4) Create the transformer.
Two files must be written at the root of the project:
- `sheetformer.ts` (the raw transformer module):
```ts title="sheetformer.ts"
// @ts-ignore
import { defineTransformer } from "@nuxt/content/transformers/utils";
import { read, utils } from "xlsx";
import { readFileSync } from "node:fs";
import { resolve } from 'node:path';
export default defineTransformer({
  name: 'sheetformer',
  extensions: ['.xlsx'],
  parse (_id: string, rawContent: string) {
    const wb = read(readFileSync(resolve("./content/" + _id.slice(8))));
    const body = wb.SheetNames.map(name => ({ name, data: utils.sheet_to_json(wb.Sheets[name])}));
    return { _id, body };
  }
});
```
- `sheetmodule.ts` (the Nuxt configuration module):
```ts title="sheetmodule.ts"
import { resolve } from 'path'
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
  setup (_options, nuxt) {
    nuxt.options.nitro.externals = nuxt.options.nitro.externals || {}
    nuxt.options.nitro.externals.inline = nuxt.options.nitro.externals.inline || []
    nuxt.options.nitro.externals.inline.push(resolve('./sheetmodule'))
    // @ts-ignore
    nuxt.hook('content:context', (contentContext) => {
      contentContext.transformers.push(resolve('./sheetformer.ts'))
    })
  }
})
```
After creating the source files, the module must be added to `nuxt.config.ts`:
```ts title="nuxt.config.ts"
import SheetJSModule from './sheetmodule'
export default defineNuxtConfig({
  // @ts-ignore
  telemetry: false,
  modules: [
    SheetJSModule,
    '@nuxt/content'
  ],
  content: {}
})
```
Restart the dev server by exiting the process (Control+C) and running:
```bash
npx -y nuxi clean
npx -y nuxi typecheck
npx -y yarn run dev
```
Loading `http://localhost:3000/pres` should show some JSON data:
```json
{
  // ...
  "data": {
    "_path": "/pres",
    // ...
    "_id": "content:pres.xlsx",
    "body": [
      {
        "name": "Sheet1", // <-- sheet name
        "data": [ // <-- array of data objects
          {
            "Name": "Bill Clinton",
            "Index": 42
          },
```
5) Create a page.  Save the following content to `pages/pres.vue`:
```html title="pages/pres.vue"
  
    {{ item.name }}
    | Name | Index | 
|---|
      
        | {{ row.Name }} | {{ row.Index }} | 
    
   
```
Restart the dev server by exiting the process (Control+C) and running:
```bash
npx -y nuxi clean
npx -y yarn run dev
```
The browser should now display an HTML table.
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 page should automatically refresh with the new content.
7) Stop the server (press `CTRL+C` in the terminal window) and run
```bash
npx -y yarn run generate
```
This will create a static site in `.output/public`, which can be served with:
```bash
npx -y http-server .output/public
```
Accessing `http://localhost:8080/pres` will show the page contents. Verifying
the static nature is trivial: make another change in Excel and save.  The page
will not change.