├── .prettierrc
├── .npmignore
├── .gitignore
├── package.json
├── src
└── injectVars.js
├── README.md
└── index.js
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "trailingComma": "all",
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Android/IJ
6 | #
7 | *.iml
8 | .idea
9 | .vscode
10 | jsconfig.json
11 |
12 | # Node.js
13 | node_modules/
14 | npm-debug.log
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # misc
5 | .DS_Store
6 | .env.local
7 | .env.development.local
8 | .env.test.local
9 | .env.production.local
10 |
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # IDE specific stuff
16 | .idea
17 | .vscode
18 | jsconfig.json
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-env",
3 | "version": "1.1.1",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "homepage": "https://github.com/formatlos/next-env",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/formatlos/next-env"
10 | },
11 | "keywords": [
12 | "next.js",
13 | "next",
14 | "plugins",
15 | "environment",
16 | "env",
17 | "dotenv",
18 | "runtime",
19 | "configuration"
20 | ],
21 | "peerDependencies": {
22 | "next": ">= 5.1.0"
23 | },
24 | "devDependencies": {
25 | "next": "^9.3.2",
26 | "webpack": "3.10.0"
27 | },
28 | "dependencies": {}
29 | }
30 |
--------------------------------------------------------------------------------
/src/injectVars.js:
--------------------------------------------------------------------------------
1 | if(window) {
2 | // next < 8 stores __NEXT_DATA__ in a js variable already
3 | var nextData = window.__NEXT_DATA__;
4 | // since next 8 __NEXT_DATA__ is stored as json
5 | var nextDataScript = document.getElementById('__NEXT_DATA__');
6 | if(nextDataScript) {
7 | nextData = JSON.parse(nextDataScript.textContent);
8 | }
9 | if (nextData && nextData.runtimeConfig) {
10 | var publicRuntimeConfig = nextData.runtimeConfig;
11 | if (!process) process = {};
12 | if (!process.env) process.env = {};
13 |
14 | Object.keys(publicRuntimeConfig).forEach(function(key) {
15 | process.env[key] = publicRuntimeConfig[key];
16 | });
17 | }
18 | }
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # next-env
2 |
3 | Automatic static (build-time) or runtime environment variables injection for [Next.js](https://github.com/zeit/next.js).
4 |
5 | The plugin doesn't handle loading of dotenv files. Use [dotenv](https://github.com/motdotla/dotenv) or [dotenv-load](https://github.com/formatlos/dotenv-load).
6 |
7 | ## Installation
8 |
9 | ```sh
10 | npm install --save next-env dotenv-load
11 | ```
12 |
13 | or
14 |
15 | ```sh
16 | yarn add next-env dotenv-load
17 | ```
18 |
19 | ## How it works
20 |
21 | Your project can consume variables declared in your environment as if they were declared locally in your JS files.
22 |
23 | By default any environment variables starting with `NEXT_STATIC_` will be embedded in the js bundles on build time.
24 | Variables starting with `NEXT_PUBLIC_` are injected on runtime (using [Next.js](https://github.com/zeit/next.js) publicRuntimeConfig internally).
25 | On node-side (SSR) all environment variables are available by default, but it is a good idea to follow the naming convention `NEXT_SERVER_`.
26 |
27 | ## Usage
28 |
29 | ### Simple
30 |
31 | This module exposes a function that allows to configure the plugin.
32 |
33 | In your `next.config.js`:
34 |
35 | ```js
36 | const nextEnv = require('next-env');
37 | const dotenvLoad = require('dotenv-load');
38 |
39 | dotenvLoad();
40 |
41 | const withNextEnv = nextEnv();
42 |
43 | module.exports = withNextEnv({
44 | // Your Next.js config.
45 | });
46 | ```
47 |
48 | In your `.env`:
49 |
50 | ```
51 | NEXT_SERVER_TEST_1=ONLY_ON_SSR
52 | NEXT_PUBLIC_TEST_1=INJECTED_BY_SSR
53 | NEXT_STATIC_TEST_1=STATIC_TEXT
54 | ```
55 |
56 | In your `pages/index.js`:
57 |
58 | ```js
59 | export default () => (
60 |
61 | - {process.env.NEXT_SERVER_TEST_1}
62 | - {process.env.NEXT_PUBLIC_TEST_1}
63 | - {process.env.NEXT_STATIC_TEST_1}
64 |
65 | )
66 | ```
67 |
68 | In the above example the output of `process.env.NEXT_SERVER_TEST_1` should only be visible until client-side rendering kicks in.
69 |
70 |
71 | ### Advanced
72 |
73 | In your `next.config.js`:
74 |
75 | ```js
76 | const nextEnv = require('next-env');
77 | const dotenvLoad = require('dotenv-load');
78 |
79 | dotenvLoad();
80 |
81 | const withNextEnv = nextEnv({
82 | staticPrefix: 'CUSTOM_STATIC_',
83 | publicPrefix: 'CUSTOM_PUBLIC_',
84 | });
85 |
86 | module.exports = withNextEnv({
87 | // Your Next.js config.
88 | });
89 | ```
90 |
91 | In your `.env`:
92 |
93 | ```
94 | CUSTOM_SERVER_TEST_1=ONLY_ON_SSR
95 | CUSTOM_PUBLIC_TEST_1=INJECTED_BY_SSR
96 | CUSTOM_STATIC_TEST_1=STATIC_TEXT
97 | ```
98 |
99 | ### with [next-compose-plugins](https://github.com/cyrilwanner/next-compose-plugins)
100 |
101 | In your `next.config.js`:
102 |
103 | ```js
104 | const withPlugins = require('next-compose-plugins');
105 | const nextEnv = require('next-env');
106 | const dotenvLoad = require('dotenv-load');
107 |
108 | dotenvLoad();
109 |
110 | const nextConfig = {
111 | // Your Next.js config.
112 | };
113 |
114 | module.exports = withPlugins([
115 |
116 | nextEnv({
117 | staticPrefix: 'CUSTOM_STATIC_',
118 | publicPrefix: 'CUSTOM_PUBLIC_',
119 | }),
120 |
121 | // another plugin with a configuration
122 | [typescript, {
123 | typescriptLoaderOptions: {
124 | transpileOnly: false,
125 | },
126 | }],
127 |
128 | ], nextConfig);
129 | ```
130 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const {
2 | PHASE_DEVELOPMENT_SERVER,
3 | PHASE_PRODUCTION_SERVER,
4 | PHASE_PRODUCTION_BUILD,
5 | } = require('next/constants');
6 |
7 | const defaultOptions = {
8 | staticPrefix: 'NEXT_STATIC_',
9 | publicPrefix: 'NEXT_PUBLIC_',
10 | };
11 |
12 | const MAIN_BUNDLE = 'main.js';
13 |
14 | function keysWithPrefix(prefix) {
15 | return Object.keys(process.env).reduce(
16 | (acc, key) => (key.indexOf(prefix) === 0 ? acc.concat([key]) : acc),
17 | [],
18 | );
19 | }
20 |
21 | function pick(keys, obj) {
22 | return keys.reduce((acc, key) => {
23 | acc[key] = obj[key];
24 | return acc;
25 | }, {});
26 | }
27 |
28 | module.exports = options => {
29 | const opts = {
30 | ...defaultOptions,
31 | ...options,
32 | };
33 |
34 | if (!opts.staticPrefix || !opts.publicPrefix) {
35 | throw new TypeError('The `staticPrefix` and `publicPrefix` options can not be empty');
36 | }
37 |
38 | return (nextConfig = {}, composePlugins = {}) => {
39 | const { nextComposePlugins, phase } = composePlugins;
40 |
41 | const nextConfigMethod = (phase, args) => {
42 | if (typeof nextConfig === 'function') {
43 | nextConfig = nextConfig(phase, args);
44 | }
45 |
46 | const newConfig = {
47 | ...nextConfig,
48 | };
49 |
50 | const publicKeys = keysWithPrefix(opts.publicPrefix);
51 | const staticKeys = keysWithPrefix(opts.staticPrefix);
52 |
53 | if (
54 | (publicKeys.length || staticKeys.length) &&
55 | (phase === PHASE_PRODUCTION_BUILD || phase === PHASE_DEVELOPMENT_SERVER)
56 | ) {
57 | Object.assign(newConfig, {
58 | webpack(config, options) {
59 | if (!options.defaultLoaders) {
60 | throw new Error(
61 | 'This plugin is not compatible with Next.js versions below 5.0.0 https://err.sh/next-plugins/upgrade',
62 | );
63 | }
64 |
65 | if (!options.isServer && publicKeys.length) {
66 | const path = require('path');
67 | const originalEntry = config.entry;
68 | config.entry = async () => {
69 | const entries = await originalEntry();
70 | const filePath = path.resolve(__dirname, 'src', 'injectVars.js');
71 | if (entries[MAIN_BUNDLE] && !entries[MAIN_BUNDLE].includes(filePath)) {
72 | entries[MAIN_BUNDLE].unshift(filePath);
73 | }
74 | return entries;
75 | };
76 | }
77 |
78 | // include static keys
79 | if (staticKeys.length) {
80 | const webpack = require('webpack');
81 | config.plugins.push(new webpack.EnvironmentPlugin(staticKeys));
82 | }
83 |
84 | if (typeof nextConfig.webpack === 'function') {
85 | return nextConfig.webpack(config, options);
86 | }
87 |
88 | return config;
89 | },
90 | });
91 | }
92 |
93 | if (
94 | publicKeys.length &&
95 | (phase === PHASE_PRODUCTION_SERVER || phase === PHASE_DEVELOPMENT_SERVER)
96 | ) {
97 | Object.assign(newConfig, {
98 | publicRuntimeConfig: {
99 | ...nextConfig.publicRuntimeConfig,
100 | ...pick(publicKeys, process.env),
101 | },
102 | });
103 | }
104 |
105 | return newConfig;
106 | };
107 |
108 | return nextComposePlugins ? nextConfigMethod(phase) : nextConfigMethod;
109 | };
110 | };
111 |
--------------------------------------------------------------------------------