Overview
- Nuxt with asyncData
- Use Fetch API instead of Axios
- Fetch JSON from same server (e.g.
/data/japan.json
) - Preload a config file which contain the file hash, which is used to map to the real path of the actual file
Json Data File
Config file with file hashes: ~/static/data/config.json
{ "file_hashes": { "placelistall-japan": "b05c11", "placelistall-tokyo": "ddd167" }}
Individual json file: e.g. ~/static/data/placelistall-japan-b05c11.json
{ "name": "Japan"}
The json data file is accessible on the same server (e.g. http://localhost:3000/data/config.json
).
Nuxt Store
Edit ~/store/index.js
- Store 2 variable:
fileHashes
from config file, andenv.RUNTIME_API_URL
(to construct full url to fetch data) - Why use nuxt store for
env.RUNTIME_API_URL
since we already useprocess.env.RUNTIME_API_URL
? Ifprocess.env.RUNTIME_API_URL
is changed during runtime on server side, it can only propagate the changes to client via nuxt store.
export const state = () => ({ env: { RUNTIME_API_URL: null // process.env.RUNTIME_API_URL }, fileHashes: null,})export const mutations = { runtimeApiUrl(state, value) { state.env.RUNTIME_API_URL = value }, fileHashes(state, value) { state.fileHashes = value },}export const actions = { nuxtServerInit({ commit }) { commit('runtimeApiUrl', process.env.RUNTIME_API_URL) },}
Preload Config
Edit ~/plugins/init-server.js
- We preload the config file in server (or static), so it is readily available to client (via nuxt store)
- For static mode, I read the config file directly from local filesystem
- For server mode, I fetch the config file from predefined
RUNTIME_API_URL
. IfRUNTIME_API_URL
is not defined, I try to guess the current server url. If the config file is accessible via filesystem on the server, you can read directly via filesystem as well (same as static).
import fs from 'fs'import url from 'url'function detectServerBaseUrl(req) { if (req.headers.referer) { const url = new URL(req.headers.referer) return `${url.protocol}//${url.host}` } else if (req.headers.host) { const protocol = req.connection.encrypted ? 'https' : 'http:' return `${protocol}//${req.headers.host}` } else if (req.connection.remoteAddress) { const protocol = req.connection.encrypted ? 'https' : 'http:' return `${protocol}//${req.connection.localAddress}:${req.connection.localPort}` } return undefined}export default async function ({ app, store, req }, inject) { let data if (process.static) { var fs = require('fs'); data = JSON.parse(fs.readFileSync('static/data/config.json', 'utf8')) } else { if (!process.env.RUNTIME_API_URL && process.server) { const serverBaseUrl = detectServerBaseUrl(req) if (serverBaseUrl) { process.env.RUNTIME_API_URL = serverBaseUrl store.commit('runtimeApiUrl', serverBaseUrl) console.log('serverBaseUrl', serverBaseUrl) } } const absoluteUrl = new URL('/data/config.json', process.env.RUNTIME_API_URL) const res = await fetch(absoluteUrl, { cache: 'no-cache' }) data = await res.json() } store.commit('fileHashes', data.file_hashes)}
Edit nuxt.config.js
.
- This only need to be run on server (or static).
export default { plugins: [ { src: '~/plugins/init-server', mode: 'server' } ]}
Fetch Helper class
- Build full url to the data
- Handle error
- Handle static, server or client fetch
Edit ~/plugins/init.js
- if static, read from local file system (if
target: static
is used) - if server, fetch from server (if
target: server
is used, you can opt to read from local file as well) - if client, fetch from server
NOTE: You will notice on client side, you can call fetch('/data.json')
without full url and it will work (I guess the client know the server path). But on server, you need to provide the full path (e.g. fetch('http://localhost:3000/data.json')
).
class Helper { test() { console.log('test') } }const $helper = new Helper()if (process.static && process.server) { $helper.getData = async ({name, store, error}) => { const hash = store.state.fileHashes[name] if (!hash) { error({statusCode: 400, message: `hash[${name}] not found`}) return } const fs = require('fs') // console.log('static') return JSON.parse(fs.readFileSync(`static/data/${name}-${hash}.json`, 'utf8')) }} else if (process.server) { $helper.getData = async ({name, store, error}) => { const hash = store.state.fileHashes[name] if (!hash) { error({statusCode: 400, message: `hash[${name}] not found`}) return } // need env RUNTIME_API_URL=http://localhost:3000 const url = new URL(`/data/${name}-${hash}.json`, process.env.RUNTIME_API_URL) // console.log('server', url.href) const res = await fetch(url) if (res.status != 200) { error({ statusCode: res.status, message: 'Post not found' }) return } return await res.json() }}else { $helper.getData = async ({name, store, error}) => { // const name = `post-${this.pageId}` const hash = store.state.fileHashes[name] if (!hash) { error({statusCode: 400, message: `hash[${name}] not found`}) return } let url = `/data/${name}-${hash}.json` // this part is not necessary as client could always resolve server base url /* if (process.env.RUNTIME_API_URL) { url = new URL(url, process.env.RUNTIME_API_URL) } */ // console.log('client', url) const res = await fetch(url) if (res.status != 200) { error({ statusCode: res.status, message: 'Post not found' }) return } return await res.json() }}export default ({ app, store }, inject) => { // inject('config', $config) inject('helper', $helper) // make sure client-side have access to process.env.RUNTIME_API_URL if (store.state.env.RUNTIME_API_URL) { process.env.RUNTIME_API_URL = store.state.env.RUNTIME_API_URL } if (process.client) { if (!process.env.RUNTIME_API_URL) { process.env.RUNTIME_API_URL = window.location.origin } }}
Edit nuxt.config.js
.
export default { plugins: [ { src: '~/plugins/init-server', mode: 'server' }, '~/plugins/init.js' ]}
Environment Variable
To pass environment variable during development, you can edit package.json
{ "scripts": { "dev": "env RUNTIME_API_URL=http://localhost:3000 nuxt", },}
NOTE: Nuxt.js dotenv Configuraton for Development and Production
NOTE: Firebase Cloud Functions Environment Variables
NOTE: Ubuntu set Environment Variable
Usage in pages
Edit ~/pages/test/_id.vue
<template> <div> <h2>Fetch using {{ $route.params.id }} to get {{ name }}</h2> <n-link to="/test/tokyo">Goto Tokyo</n-link> </div></template><script>export default { async asyncData ({ $helper, params, store, error }) { const name = `placelistall-${params.id}` const data = await $helper.getData({name, store, error}) return data }}</script>
Access via http://localhost:3000/test/japan
NOTE: You can use Nuxt Fetch if you need more control over asyncData
.