Weekly April #2025

17. apríla 2025

Just another day of developer before holidays…

  1. Nothing Lasts Forever. Not Even 302 expires max
  2. It should be Nuxt middleware redirect
  3. Nuxt, Node 16, and the –openssl-legacy-provider Headache
  4. GitLab SSH key – Permission denied (publickey) fix
  5. Bash gotcha: When your .env file disappears
  6. WordPress cache issues
  7. WordPress CSS tr::after { content: “; } overflow
  8. Broken Padding with env()

1. Nothing Lasts Forever. Not Even expires max.

So here’s what happened.
My colleague (whom I still respect, mostly) set up a temporary 302 redirect in Nginx. All good — except he also gave it a little gift:

location / {
    return 302 /video;
    expires max;  # 😬
}

Yep. expires max. On a 302.
The result? Browsers locked that redirect in like it was written in blood. Even after we removed the redirect on the server, users were still being thrown to /video as if nothing had changed. Why? Because cache is a liar with a great memory.

The Fix?

First, remove that cursed line:

location / {
    return 302 /video;
    # expires max; ← 🔥 delete this with prejudice
}

Then, since users already had the redirect cached, I dropped this little JS bomb on /video:

<script>
window.addEventListener('load', () => {
  if (!localStorage.getItem('redirect-fix-done')) {
    fetch('/', { cache: 'reload' })
      .finally(() => localStorage.setItem('redirect-fix-done', '1'));
  }
});
</script>

One silent fetch, and browsers finally got the memo. Redirect gone, cache busted, sanity restored.

Moral of the story?
Nothing lasts forever.
Not even expires max — if you fight back.

2. It should be Nuxt middleware redirect

import { getRouteName, ROUTES } from '@/constants/routes';

const RELEASE_DATE = new Date('2025-05-01 00:00:00');

export default function middlewareVideo({ route, redirect, store }) {
    if (new Date() > RELEASE_DATE) return;

    const ALLOWED_ROUTES = new Set([
        ROUTES.LOGIN,
        ROUTES.PASSWORD.RESET,
        ROUTES.PASSWORD.TOKEN,
    ]);

    if (!ALLOWED_ROUTES.has(getRouteName(route)) && !store.getters['user/isLogged']) {
        return redirect('/video');
    }
}

and then on the /video page we got a countdown set to same RELEASE_DATE. On the end it just redirect to /. Simple.

Just add that to nuxt.config.js:

...
router: {
    middleware: ['redirect-to-video'],
},
...

3. Nuxt, Node 16, and the –openssl-legacy-provider Headache

While working on a legacy Nuxt 2 project, I ran into a cryptic error:

/usr/local/n/versions/node/16.15.0/bin/npm run dev
node: --openssl-legacy-provider is not allowed in NODE_OPTIONS

The root cause? A mismatch between the Node and npm versions defined in package.json and those actually used by the project. The fix was simple: I ran n auto, which automatically set the correct Node and npm versions based on engines. After that, the error disappeared when running from the terminal.

However, in PHPStorm, the error persisted. The solution there was to check the Run/Debug Configurations and remove the NODE_OPTIONS=--openssl-legacy-provider environment variable, if present. Once that was cleaned up, everything worked as expected.

Small issue, quick fix — but worth documenting.

4. GitLab SSH key – Permission denied (publickey) fix

When working with GitLab over SSH, I hit a Permission denied (publickey) error even though everything seemed correctly set up. The actual issue? My old SSH key had expired, and GitLab no longer accepted it.

  1. Generate a new SSH key:
ssh-keygen -t ed25519 -C "super@superdeveloper.sk"
  1. Update ~/.ssh/config to use the new key for GitLab:
Host gitlab.com
    HostName gitlab.com
    User git
    IdentityFile ~/.ssh/id_ed25519_2025

After that, SSH auth worked again. Don’t forget to add the new public key (.pub) to your GitLab account under Settings → SSH Keys.

5. Bash gotcha: When your .env file disappears

While working on a simple start.sh script to run a Docker container with a bound .env file, I encountered a surprisingly tricky problem: the script needed to work regardless of where it was executed from — manually, from an IDE like PHPStorm, or via absolute/relative paths.

The goal was simple: always mount the .env file located next to the script itself.

❌ What didn’t work

Here’s how I originally tried to bind the file:

#!/bin/bash

docker run --rm \
  --mount type=bind,source="$(pwd)/.env",target=/usr/local/super/.env,readonly \
  $IMAGE_NAME

This works fine if you run the script from the correct directory. But when executed from another location or via PHPStorm, $(pwd) no longer points to where the script lives — and the mount fails silently.

✅ The fix: use the script’s own path

To make the script fully portable, I replaced $(pwd) with:

SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)"

This reliably resolves the absolute path to the directory where the script resides — even on macOS and when run from PHPStorm.

🧪 Final working version

#!/bin/bash

SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="$SCRIPT_PATH/.env"

if [[ ! -f "$ENV_FILE" ]]; then
  echo "❌ .env file not found at $ENV_FILE"
  exit 1
fi

echo "✅ Using .env from: $ENV_FILE"

docker run --rm \
  --mount type=bind,source="$ENV_FILE",target=/usr/local/super/.env,readonly \
  $IMAGE_NAME

🧠 Lesson learned

Don’t trust pwd in your scripts. Always resolve paths relative to the script, not the shell. Your future self (and your IDE) will thank you.

6. WordPress cache issues

I don’t even want to talk about it. I have nothing against WordPress—it’s (kinda) a good CMS when used properly. But we’re dealing with a legacy e-shop using WooCommerce, WP Bakery plugin, and the Porto theme. Even something as simple as changing cart buttons is a pain (thanks to the previous owners and a mix of plugins). After I managed to deploy the update relatively quickly, nothing changed on the front end. Purging every cache we could find didn’t help. Eventually, renaming the CSS file worked (changing the enqueued style versions didn’t), but it also broke the homepage. So, more cache purging, more minifying plugins, and plenty of head-scratching followed.

7. WordPress CSS tr::after { content: “; } overflow

Already on my way out of the apartment when my colleague DM’d me: „Add to cart with gifts on mobile is broken.“ Of course, it worked perfectly on desktop. But on iPhone, there was this overlay covering the entire cart, making it impossible to click on the gift items. I connected my phone to the Mac and opened Safari’s Developer Tools. Turns out, the issue was with a tr::after { content: ""; } that was meant to overlay a tr with a disabled class. The approach seemed fine, but not all browsers were rendering it properly, even when the tr was set to relative and the overlay was absolute with top/left: 0 and width/height: 100%.

So, a quick fix: remove that content tr::after { content: none; } and instead apply opacity: 0.5 to the tr.disabled. Same result, but a simpler and more importantly, cross-browser working solution. I sent my colleague those two lines of code and ran away, leaving them to play the WordPress cache game.

8. Broken Padding with env()

Last problem of the day was when my junior colleague asked why this CSS rule wasn’t working on some older devices:

padding-left: max(56px, env(safe-area-inset-left));

It looked fine at first — but on certain devices (especially older Androids or embedded WebViews), the result was:

padding-left: 0px;

Why? Because some browsers don’t support env(), and when they hit an unknown value inside max(), they can ignore the entire rule.


📐 Understanding the Design Was the First Problem

She told me: „In the design, it’s exactly 56px from the left.“
Yes, but the design was done on a notch device, like an iPhone with a dynamic island. That meant the 56px included the safe area.

When you hardcode 56px, it feels too wide on non-notch devices.
When you rely on env(), it breaks on devices that don’t support it.
Classic responsive trap.


✅ The Real Fix: Separate Layout and Safe-Area Padding

So we aligned with the designer:
Let’s use 20px layout padding, and if the device has a notch, just add the safe area on top of that.

On notch devices: 20px + safe-area
On others: just 20px

Here’s the pattern:

<div class="safe-area-wrapper">
  <div class="content">
    <!-- your content -->
  </div>
</div>

.safe-area-wrapper {
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}

.content {
  padding-left: 20px;
  padding-right: 20px;
}

This way, you always get at least 20px padding — and if the device has a notch or curved edge, it adds that space without breaking the layout.


Pridaj komentár