├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .nuxtrc ├── CHANGELOG.md ├── README.md ├── package.json ├── playground ├── app.config.ts ├── app.vue ├── nuxt.config.ts └── package.json ├── pnpm-lock.yaml ├── src ├── module.ts └── runtime │ └── plugin.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs/eslint-config-typescript" 4 | ], 5 | "rules": { 6 | "@typescript-eslint/no-unused-vars": [ 7 | "off" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=false 2 | typescript.includeWorkspace=true 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## v0.1.1 5 | 6 | 7 | ### 🩹 Fixes 8 | 9 | - Handle static hydration (e07a589) 10 | 11 | ### ❤️ Contributors 12 | 13 | - Pooya Parsa 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Remote Config 2 | 3 | [Under Development] 4 | 5 | ## Why? 6 | 7 | You create and deploy an amazing [Nuxt App](https://nuxt.com/). Then you need to change few parameters, maybe a message in the header to users, changing theme variant based on an special occasion, or take site down for maintenance. 8 | 9 | Typically, it is done by either commiting a new change to the website and waiting deployment or using a hosted CMS service and server-side-rendering to apply the config. 10 | 11 | This module makes it super easy by removing all complexity of setting up a remote CMS and handling different rendering modes by providing a super easy approach to dynamically change your application configration using Nuxt 3 [app config](https://nuxt.com/docs/guide/directory-structure/app.config). 12 | 13 | ## Usage 14 | 15 | 1. Install `nuxt-remote-config` as dev dependency: 16 | 17 | ```sh 18 | # npm 19 | npm i -D nuxt-remote-config 20 | 21 | # pnpm 22 | pnpm add -D nuxt-remote-config 23 | 24 | # yarn 25 | yarn add nuxt-remote-config 26 | ``` 27 | 28 | 2. Add module to `nuxt.config`: 29 | 30 | ```js 31 | export default defineNuxtConfig({ 32 | module: [ 33 | 'nuxt-remote-config' 34 | ] 35 | }) 36 | ``` 37 | 38 | 3. Setup config registry (see next sections) 39 | 40 | ## Configuration Registry 41 | 42 | In order to use this module on your website, you need a configuration registry. 43 | 44 | ### OpenKV 45 | 46 | > Experimental: OpenKV is under development and service usage might change. Anybody can edit the config at the moment! 47 | 48 | - Head to https://openkv.unjs.io/ and create a namespace and take the id 49 | 50 | Define in `nuxt.config`: 51 | 52 | ```js 53 | export default defineNuxtConfig({ 54 | remoteConfig: { 55 | id: '' 56 | } 57 | }) 58 | ``` 59 | 60 | Alternatively create `.nuxtrc` and set namespace id: 61 | 62 | ```ini 63 | remoteConfig.id= 64 | ``` 65 | 66 | ## Custom Registry 67 | 68 | You custom registry's url should respond to `/{key}` with a JSON string: 69 | 70 | Define in `nuxt.config`: 71 | 72 | ```js 73 | export default defineNuxtConfig({ 74 | remoteConfig: { 75 | url: 'https://my-custom-registry.com' 76 | } 77 | }) 78 | ``` 79 | 80 | Alternatively create `.nuxtrc` and set url: 81 | 82 | ```ini 83 | remoteConfig.url=https://my-custom-registry.com 84 | ``` 85 | 86 | **Note:** You can use `{id}` placeholder that is replaced with `id` configuration. 87 | 88 | ## Module Options 89 | 90 | ### `endpoint` 91 | 92 | Registry endpoint. You can customize it at runtime using `NUXT_REMOTE_CONFIG_ENDPOINT` 93 | 94 | ## Development 95 | 96 | - Run `npm run dev:prepare` to generate type stubs. 97 | - Use `npm run dev` to start [playground](./playground) in development mode. 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-remote-config", 3 | "version": "0.1.1", 4 | "license": "MIT", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "import": "./dist/module.mjs", 9 | "require": "./dist/module.cjs" 10 | } 11 | }, 12 | "main": "./dist/module.cjs", 13 | "types": "./dist/types.d.ts", 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "prepack": "nuxt-module-build", 19 | "dev": "nuxi dev playground", 20 | "dev:build": "nuxi build playground", 21 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground", 22 | "test": "true", 23 | "release": "pnpm test && changelogen --release && git push --follow-tags && npm publish" 24 | }, 25 | "dependencies": { 26 | "@nuxt/kit": "^3.0.0" 27 | }, 28 | "devDependencies": { 29 | "@nuxt/module-builder": "^0.2.1", 30 | "@nuxt/schema": "^3.0.0", 31 | "@nuxtjs/eslint-config-typescript": "^12.0.0", 32 | "@types/node": "^18.11.9", 33 | "changelogen": "^0.4.0", 34 | "defu": "^6.1.1", 35 | "eslint": "^8.27.0", 36 | "nuxt": "^3.0.0", 37 | "unctx": "^2.1.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /playground/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineAppConfig } from '#imports' 2 | 3 | export default defineAppConfig({ 4 | site: { 5 | title: 'Haarlem Grocery Store', 6 | description: 'Grocery store in Haarlem', 7 | openingHours: 'Mo-Fr 08:00-18:00' 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 36 | 37 | 48 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | import MyModule from '..' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | MyModule 7 | ], 8 | routeRules: { 9 | '/static': { prerender: true }, 10 | '/swr': { swr: true }, 11 | '/spa': { ssr: false } 12 | }, 13 | remoteConfig: { 14 | id: '89f669294c79463a9454b42bd3c82b9b' 15 | }, 16 | nitro: { 17 | prerender: { 18 | routes: ['/static', '/spa'] 19 | } 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nuxt-remote-config-playground" 4 | } 5 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | import { defineNuxtModule, addPlugin, createResolver, addTemplate } from '@nuxt/kit' 3 | 4 | export interface ModuleOptions { 5 | url: string 6 | keys: string[] 7 | id: string 8 | } 9 | 10 | export default defineNuxtModule({ 11 | meta: { 12 | name: 'remote-config', 13 | configKey: 'remoteConfig' 14 | }, 15 | defaults: { 16 | url: 'https://openkv.unjs.io/kv/{id}/config:', 17 | keys: ['site'], 18 | id: '' 19 | }, 20 | setup (options, nuxt) { 21 | const { resolve } = createResolver(import.meta.url) 22 | const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) 23 | nuxt.options.build.transpile.push(runtimeDir) 24 | addPlugin(resolve(runtimeDir, 'plugin')) 25 | nuxt.options.runtimeConfig.public.remoteConfig = options 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /src/runtime/plugin.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin, useRuntimeConfig, updateAppConfig, callWithNuxt } from '#app' 2 | 3 | export default defineNuxtPlugin(async (nuxtApp) => { 4 | const { remoteConfig: { keys, url, id } } = useRuntimeConfig().public 5 | 6 | const _updateConfig = (config: any) => callWithNuxt(nuxtApp, updateAppConfig, [config]) 7 | 8 | const pullConfig = async (_keys?: string[]) => { 9 | const rConfig: any = {} 10 | await Promise.all((_keys || keys).map(async (key) => { 11 | const _url = url.replace('{id}', id) + key 12 | rConfig[key] = await fetch(_url).then(r => r.json()).catch((err) => { 13 | // eslint-disable-next-line no-console 14 | console.warn(`Cannot fetch remote config ${key} from ${_url}: ` + err) 15 | }) 16 | _updateConfig({ [key]: rConfig[key] }) 17 | })) 18 | return rConfig 19 | } 20 | 21 | if (process.server) { 22 | nuxtApp.payload.appConfig = await pullConfig() 23 | } 24 | 25 | if (process.client) { 26 | if (nuxtApp.payload.appConfig) { 27 | _updateConfig(nuxtApp.payload.appConfig) 28 | } 29 | if (nuxtApp.payload.prerenderedAt) { 30 | nuxtApp.hook('app:mounted', async () => { 31 | await pullConfig() 32 | }) 33 | } else if (!nuxtApp.payload.serverRendered || !nuxtApp.payload.appConfig) { 34 | await pullConfig() 35 | } 36 | } 37 | 38 | return { 39 | provide: { 40 | $remoteConfig: { pull: pullConfig } 41 | } 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | --------------------------------------------------------------------------------