NextJS - Get rid of DotENV
Tags: nextjs, typescript, dotenv, conf
Published: 2022-03-31 12:27:26

I always disliked the **.env** file. Almost every time I was forced to place it on the top level of my app directory. At some point i started to use the npm [config](https://www.npmjs.com/package/config) package. This gives the application a consistent configuration interface and there is a formidable way to implement it into the NextJS environment. For people which prefer code over text, feel free to checkout the finished [implementation](https://github.com/LaCocoRoco/examples-nextjs/tree/master/nextjs-config).

## Implementation
As reference we usa a [NextJS Typescript zero configuration](https://nextjs.org/docs/basic-features/typescript) setup.

```
npx create-next-app@latest --ts
# or
yarn create next-app --typescript
```

First we have to install the necessary npm package.
```
npm install config @types/config
```

Now we create the config directory and configuration files. The **default.yaml** will be used later as a type template.
```
config
- default.yaml
- development.yaml
- production.yaml
```

NextJS has the possibility to define [environment variables](https://nextjs.org/docs/api-reference/next.config.js/environment-variables) in the config file. To make full use of it, we will import the config package and set the environment variables to the configuration variables with **config.util.toObject()**. By default NextJS creates a **next.config.js** file in the root directory. Change the extension to **.mjs** and replace the content with the following.

```javascript
// next.config.mjs

import config from 'config';

const nextConfig = {
  reactStrictMode: true,
  env: config.util.toObject()
}
```

At this point we already replaced the **.env** files. NextJS will load the configuration files from the config folder with the corresponding activ environment.

To use it with typescript we have to do some additional work. First we will need a type declaration file for the config variables. Then we have to add it to the global process environment. 

From here on out we will work with an example for better traceability.
Let us put some stuff into the **default.yaml** file.

```yaml
firebase:
  apiKey: apiKey
  appId: appId
  projectId: projectId
  authDomain: authDomain
  databaseURL: databaseURL
  storageBucket: storageBucket
  messagingSenderId: messagingSenderId
  measurementId: measurementId
```

Next we will have to reflect the configuration variables in typescript. In my native language there is a saying:
> Viele Wege führen nach Rom

In short it means there are many solutions for a problem. For example, we could do it manually or use a web based [yaml to typescript](https://jsonformatter.org/yaml-to-typescript) converter. Unfortunately i could not find any npm package for this problem. But as many of us know: **Why do something in 10 minutes when you can automate it in 10 hours?** Consequently, let us install an additional package.

```
npm install -D ymlts
```

Now we can convert the configuration file to typescript from our console. With the following command we create the necessary type file.

```
npx ymlts config/default @types/config 
```

This will create a **config.d.ts** in the **@types** directory. Its content should look like this.

```typescript
// @types/config.d.ts

interface Config {
    firebase: Firebase;
}

interface Firebase {
    apiKey:            string;
    appId:             string;
    projectId:         string;
    authDomain:        string;
    databaseURL:       string;
    storageBucket:     string;
    messagingSenderId: string;
    measurementId:     string;
}
```

Last but not least, we have to add the new config type to the process environment. For this we create a **global.d.ts** file in the **@types** directory...

```
@types
- global.d.ts
```

...and extend the process environment with the config type.

```typescript
// @types/global.d.ts

namespace NodeJS {
  interface ProcessEnv extends Config { }
}
```

For future use, if you are really lazy: It is possible to add the ymlts command to the **package.json** and start it with **npm run env**.

```json
// package.json

"scripts": {
  "env": "npx ymlts config/default @types/config",
}
```

## Conclusion
So why? At first glance this looks like unnecessary work, but there is some neat stuff!

* Better readability with a humanfriendly YAML file.

* Environment variables managed in an organized config folder.

* Normaly you have to prefix Environment Variables with **NEXT_PUBLIC_** to expose them to the [browser](https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser). But NextJS replaces Environment Variables at [buildtime](https://nextjs.org/docs/api-reference/next.config.js/environment-variables) which should make it obsolete.

* By converting the config file to types we get the possibility to use intellisense with **process.env**.

* The YAML configuration variables can be nested. Related to this blog as example it is possible to initialize  the Firebase app with `initializeApp(process.env.firebase);`.