Arbeit

NextJS Environment Variables in Containers

Are you working with Containers, that you build once and then push through the various stages?

If so, then this might be for you as you probably also need some if not all of your env variables available on the client side as well. Honestly I was expecting this to be the case when using NEXT_PUBLIC_* as a prefix for them…

What NextJS offers out of the box

Configuration is most of the time added at build time for next, this changed over time as originally all of them where available through the publicRuntimeConfig and serverRuntimeConfig. Having things at build time is sadly not really an option when deploying through containers as it is not reasonable to build every time the container starts…

For one of my applications that means that with all the build logic I have around 1.2Gb for the container, whereas when using the standalone output option the container size goes down to 82Mb!!!

What was and is now deprecated?

The old way for environment variables, the runtime config, was deprecated with the arrival of the app dir.

  • This feature is deprecated. We recommend using environment variables instead, which also can support reading runtime values.
  • You can run code on server startup using the register function.
  • This feature does not work with Automatic Static Optimization, Output File Tracing, or React Server Components.
https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration

How the runtime config was defined in next.config.js:

/** @type {import('next').NextConfig} */
const config = {
  publicRuntimeConfig: {
    variableForClientSide: process.env.ANYTHING,
  },
  serverRuntimeConfig: {
    variableForServerSide: process.env.ANYTHING_SECRET,
  }
}

This has been working most of the time, but also had it’s quirks, especially since some parts of the runtimeConfig would be inlined by NextJS – this would happen every time when a page is rendered and then outputted.

This caching might seem useful in some cases, but makes it really tricky to reliable get a good output. (ISR was partially helpful, but needed all paths to be accessed before any clients would be allowed to access it as it would otherwise output stale runtime variables from the build time!!)

To have this working it was required to have a getInitialProps either on every page or the _app.tsx to force rendering on build time.

Using process.env

Next.js comes with built-in support for environment variables, which allows you to do the following:

  • Use .env.local to load environment variables
  • Bundle environment variables for the browser by prefixing with NEXT_PUBLIC_
https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables

This sounds perfect at the beginning, but starts to fall apart the moment you want to deploy in any kind of containerised system. Within your application you can reference any environment variable as process.env.VARIABLE, with the note that this implies a build time replacement of the variable (except for SSR, there it might be read at runtime…).

Default Environment Variables

This is the default behaviour that I would not expect, as this will replace the variables at build time by replacing any occurrence of process.env.NEXT_PUBLIC_VAR with the actual value.

On the server side, the variables are not inlined and can still be referenced – and therefor be used especially in the app router!

Dynamic Environment Variables

If variables should not be replaced and always be loaded from the corresponding env variable then they must be defined so it seems like the name could change.

// This will NOT be inlined, because it uses a variable
const varName = 'NEXT_PUBLIC_ANALYTICS_ID'
setupAnalyticsService(process.env[varName])

// This will NOT be inlined, because it uses a variable
const env = process.env
setupAnalyticsService(env.NEXT_PUBLIC_ANALYTICS_ID)

Nevertheless this will not behave as expected on the client side as process.env will always be an empty object. (it works fine on the server side!!). DANGER!!

Make process.env work in a containerised setup

First of, what exactly do I mean by this:

  • process.env.NEXT_PUBLIC_* should reflect on client side whatever was set at container start
  • process.env.* should reflect on the server side whatever was set at container start
  • no build should be required to have capability 1 and 2!

There are many tickets in the NextJS bugtracker, but so far I have not found any that provided a solution for this. But fear not, I have found a way! (here are some of them all related to env handling, had problems with all of them at some point, some have been fixed, some need the steps that will be outlined at the end!)

Code

// file: env.provider.tsx
import { EnvProviderClient } from './env.provider.client'
import { FC } from 'react'

export const EnvProvider: FC = () => {
  const env = {}

  Object.keys(process.env).forEach(key => {
    if (key.startsWith('NEXT_PUBLIC_')) {
      env[key] = process.env[key]
    }
  })

  return <EnvProviderClient env={env} />
}
// file: env.provider.client.tsx
'use client'

import { FC, useMemo } from 'react'

interface Props {
  env: any
}

export const EnvProviderClient: FC<Props> = ({ env }) => {
  useMemo(() => {
    if (typeof window !== 'undefined') {
      global.env = env

      window.dispatchEvent(new Event('global.env'))
    }
  }, [])

  return null
}

With those 2 files we can fix the default behaviour and expose all public env variables to the client! In case you need to update configuration if env is not yet set, use an event listener!

To then enable the client side env variables you just have to add <EnvProvider /> and they will all be available.

Problem solved!! This also works with standalone builds using tracing (https://nextjs.org/docs/app/api-reference/next-config-js/output) – this will also reduce your image size dramatically!!

Allgemein, Arbeit

Unit tests for SF2 with IntelliJ / PHPStorm and Vagrant

you require a working Symfony instance running through Vagrant.

Prerequisites

1) Install Remote Interpreter

remote_php

2) Add a Remote PHP Interpreter.

use vagrant ssh-config to get required information

Screen Shot 2016-02-17 at 14.11.57

3) Setup Path Mapping (Deployment Server)

Screen Shot 2016-02-17 at 14.47.11

 

4) Setup PHPUnit

Screen Shot 2016-02-18 at 00.04.27.png

create a file in app/phpunit.php and use it as a custom loader for phpunit:

<?php

if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
    define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/autoload.php');
}

require_once 'autoload.php';

this allows you to use @runInSeparateProcess till https://youtrack.jetbrains.com/issue/WI-29458 is fixed

 

 

Now everything should be setup and you can run the tests in Vagrant 🙂

Allgemein, Arbeit

from Ubuntu to OSX

Over the years I moved from windows to linux and now to osx. Why you might ask?

Well, this is simple: I no longer have the time to configure the system to work the way I want it to. It is just supposed to do as much as possible without my intervention.

Ubuntu came pretty close to that but sadly my old vaio was breaking down.

 

I’m using OSX now for a month and I’m more that just happy! There are things missing like the “select and paste with middle mouse button” that I’m used from ubuntu and the option to move windows with the mouse and the keyboard, but it is close enough.