Web Development
Debugging NextJS 14 with VSCode

Debugging NextJS 14 with VSCode

Using debugging tools when you're developing is something that in my mind is absolutely essential, and I'm shocked whenever I see people response.writing out values to work out what's going wrong in there application. In the past I've written about debugging VueJS in VS Code and the setup for NextJs is somewhat similar.

With VS Code you have launch.json file which instructs how VS Code should start an application in debug mode when you hit F5.

Launch.json for NextJs 14 using app router

The launch.json file for NextJs 14 is very simple and consists of the following:

2 "version": "0.2.0",
3 "configurations": [
4 {
5 "name": "Next.js: debug full stack",
6 "type": "node-terminal",
7 "request": "launch",
8 "command": "npm run dev",
9 "serverReadyAction": {
10 "pattern": "- Local:.+(https?://.+)",
11 "uriFormat": "%s",
12 "action": "debugWithChrome"
13 }
14 }
15 ]

Launch.json for NextJs 13 using page router

The launch.json file for NextJs13 is slightly different. This one also includes an attach option which will attach to the running process if you've already manually started NextJs from the command line.

2 "version": "0.2.0",
3 "configurations": [
4 {
5 "name": "Next.js: debug full stack",
6 "type": "node-terminal",
7 "request": "launch",
8 "command": "npm run dev",
9 "console": "integratedTerminal",
10 "serverReadyAction": {
11 "pattern": "started server on .+, url: (https?://.+)",
12 "uriFormat": "%s",
13 "action": "debugWithChrome"
14 }
15 }
16 ]

This file will also work for NextJs 14, however I found that any component marked as "use client" doesn't hit any of the breakpoints in client side code. It also doesn't trigger chrome to open when the application starts.

Custom font impact on CLS

Custom font impact on CLS

Recently I wrote about pre-loading fonts to improve CLS (Cumulative Layout Shift) scores and while that will go a long way to improving CLS as well as limiting the amount of flicker your users will experience. It still leaves an issue that not all fonts are the same size, and therefore can take up varying amounts of space.

Take a look at these two examples of a heading that contains the same text and font size but with different font family's.

Custom font heading
Base font heading on 2 lines

The first is using a custom font that requires a download to happen before it is shown, while that happens the second will be displayed.

As you can see the custom font has much narrower letters meaning more text will fit on each line, whereas the base font goes onto two lines and creates CLS when the fonts swap.

Fixing Line Heights

As well as widths fonts can have different heights, fortunately, this is relatively simple to fix by including line heights in your CSS.

1.customFont {
2 font-size: 24px;
3 line-height: 1rem;
4 font-family: bebasneuepro-bold, sans-serif;

Fixing Font Widths with Size-Adjust

It's not possible to make one font the exact size of another, but we can get close using size adjust.

Size-adjust allows you to set a percentage to scale a font by and can be applied on a font-face definition. This will affect both the height and width of the font, but if you've fixed the line height then the font getting smaller in height won't make the overall content shorter. With size adjust, we are aiming to match the horizontal spacing so that line breaks happen at the same point, leaving us with an equal amount of lines irrespective of font and therefore no CLS.

An important aspect is that we are creating a font-face for a local font that will load instantly. As you can see my custom font loads from a URL and I've created a fallback font-face loading san-serif from local.

The custom font class now includes my fallback font in the font-family rather than sans-serif directly.

1 @font-face {
2 font-family: 'bebasneuepro-bold';
3 src:
4 url('/fonts/bebasneuepro-bold-webfont.woff2') format('woff2'),
5 url('/fonts/bebasneuepro-bold-webfont.woff') format('woff');
6 font-display: swap;
7 }
9 @font-face {
10 font-family: bebasneuepro-bold-fallback;
11 src: local('sans-serif');
12 size-adjust: 62%;
13 }
15.customFont {
16 font-size: 24px;
17 line-height: 1rem;
18 font-family: bebasneuepro-bold, bebasneuepro-bold-fallback;

The effect is the heading text now takes up the same width irrespective of font and stays on one line. As the line height has also been set the overall height of the element stays the same and there is no CLS impact.

Why should you preload fonts?

Why should you preload fonts?

There are two ways you can arrive at wanting to preload fonts on a webpage.

The first is your running a lighthouse test for page speed, and scoring badly for cumulative layout shift (CLS). This is the measure of how long elements on the page are shifting position as the page is loaded. This can be impacted by a font load causing elements on a page due to custom fonts having a different size to the default browser font initially used in rendering.

The second is that you can visually see the font switching from default to custom and want to eliminate this.

Now the best way to eliminate both of these issues is to just use browser fonts from the start and that way no font needs to be downloaded in the first place, but the chances are that's not an acceptable solution for your stakeholders.

What is the normal browser behavior?

To use a custom font you first need to define that font as a @font-face in CSS. It will look something like this.

1@font-face {
2 font-family: "Bitstream Vera Serif Bold";
3 src: url("https://mdn.github.io/css-examples/web-fonts/VeraSeBd.ttf");
6body {
7 font-family: "Bitstream Vera Serif Bold", serif;

You will likely define this in a stylesheet file referenced from your HTML page. This means that the browser will first download the HTML page containing text to display, but won't have any way of knowing about the font until the CSS file has been downloaded.

What's worse, the browser is also going to try lazy loading all the font's in. Meaning just because it see's the font-face definition in the stylesheet, it's not going to do anything with it until it knows it's going to be applied to a style on the page, and only then will it start downloading the font.

How to preload a font

To avoid these issues you can tell the browser to preload the font by adding a Link element to the head of the document.

2 <!-- ... -->
3 <link rel="preload" href="/assets/Pacifico-Bold.woff2" as="font" type="font/woff2" crossOrigin='anonymous'>

This link element tell the browser that the font is going to be used and it should download it as early as possible. Lets break down the attributes:

link - tells the browser to preload the font.

href - the url of the file to download.

as and type- what type of file this is. In this case its a font with a mime type of font/woff2.

crossOrigin - an interesting quirk with browsers, unless you set this the file wont get downloaded even if your hosting the font yourself.

Best Practices with Fonts

Here are some more best practices to follow with fonts.

Minimize the number of custom fonts

The best way to avoid issues with loading font's is to use as few custom fonts as possible. Essentially the more you add the more that will need to be downloaded.

Although fonts can be preloaded via preloading, you are only ever optimizing the critical path to show the webpage, it won't do anything to reduce the total data needing to be downloaded. A browser is also limited in the number of requests it will make at a time, meaning the more files there are, the more that will ultimately get queued.

Only Preload the main font

To ensure browser support your font-face definition might actually contain multiple font files of different types.

1@font-face {
2 font-family: 'MyWebFont';
3 src: url('myfont.woff2') format('woff2'),
4 url('myfont.woff') format('woff');

When the browser reads these definitions they will pick the first in the list with a format that they support and ignore the rest. However if you add a Link tag to preload each of them, the browser will have no way of knowing it is an order of preference and download them all. Therefore you should only preload the first in the list. This will likely be a woff2 that's supported by basically all modern browsers.

Security Headers in Next.JS

Security Headers in Next.JS

To ensure your site is protecting its users. several security headers can be set to help prevent certain attacks against websites.

For Next.JS these are set in the next.config.js file. Typically I use the following config.

1/** @type {import('next').NextConfig} */
2const nextConfig = {
3 async headers() {
4 return [
5 {
6 source: '/:path*',
7 headers: [
8 {
9 key: 'X-Frame-Options',
10 value: 'SAMEORIGIN',
11 },
12 {
13 key: 'Content-Security-Policy',
14 value: "frame-ancestors 'none'",
15 },
16 {
17 key: 'Referrer-Policy',
18 value: 'same-origin',
19 },
20 {
21 key: 'Strict-Transport-Security',
22 value: 'max-age=15768000',
23 },
24 {
25 key: 'X-Content-Type-Options',
26 value: 'nosniff',
27 },
28 {
29 key: 'X-XSS-Protection',
30 value: '1; mode=block',
31 },
32 ],
33 },
34 ];
35 },
38module.exports = nextConfig;


Setting frame-ancestors: none is similar to X-Frame-Options and will prevent a site loading in an ancestor frame, iframe. object or embed.


Indicates whether a browser should be allowed to render a page in a frame, iframe, embed or object tag. By setting to SAMEORIGIN this ensures the site is only rendered in this way on itself and not on other sites.

Click-jacking attacks commonly use display other sites within themselves to fool a user as to what site they are on.


Referrer Policy sets how much information is sent with the referrer header. By setting to same-orign, the referrer header is only sent on same origin requests.


This header instructs the browser to only access the site over https. By setting the age it instructs the browser to remember this setting for the number of seconds given.


Setting to nosniff will block a request if the destination is of type style and the MIME type is not text/css, or of type script and the MIME type is not a JavaScript MIME type.


This is a non-standard header and isn't supported in any current browsers but has history support with Chrome and Edge.

The header instructs the browser how to handle any cross-site scripting attacks it detects on the page. Setting to 1; mode-block will prevent the page from rendering if an attack is detected.