Viewport meta tag in NextJS 14 app router with "shrink-to-fit=yes" issue

245 Views Asked by At

I'm trying to make my NextJS 14 app router application scale down on any device less than 500px width, as I'm making my app responsive down to 500px wide and anything below that I want it to scale down, previously I've used min-width: 500px; in the body CSS and used this tag inside the <head> tag:

<meta name="viewport" content="width=500, shrink-to-fit=yes, maximum-scale=1, user-scalable=0" />

And that would work on Chrome and Safari mobile apps and everywhere else, however now with NextJS 14, I can't set this tag directly in the <head> tag inside the layout.tsx file as there's an already defined viewport tag and this new one will be a duplicate and won't take effect, the new way of setting and changing this default viewport tag is changed to this way instead:

import type { Viewport } from 'next'
 
export const viewport: Viewport = {
  width: 500,
  maximumScale: 1,
  userScalable: false,
}

Reference: https://nextjs.org/docs/app/api-reference/functions/generate-viewport

But this doesn't support shrink-to-fit=yes and without adding it, the app would be zoomed in on Safari browser and some other browsers with some horizontal scroll.

Is there a way to set the metatag with shrink-to-fit=yes or another way to fix this issue of being zoomed in on Safari Mobile view when loading the page?


For the ViewportLayout, NextJS 14 have the following:

export type ViewportLayout = {
    width?: string | number;
    height?: string | number;
    initialScale?: number;
    minimumScale?: number;
    maximumScale?: number;
    userScalable?: boolean;
    viewportFit?: 'auto' | 'cover' | 'contain';
    interactiveWidget?: 'resizes-visual' | 'resizes-content' | 'overlays-content';
};

Would any of these combinations solve the issue mentioned in the Safari mobile browser? or is it possible to extend this layout to include shrink-to-fit?

Otherwise, how do developers usually approach this case where they want to scale down their websites for smaller screens on different browsers in NextJS 14 with app router?

1

There are 1 best solutions below

11
VonC On

The Viewport type from Next does not include shrinkToFit

That means... a custom solution: manually injecting the viewport meta tag to include the necessary properties.

Next.js 13.3 and before: You would need to a custom Document (_document.js or _document.tsx if you are using TypeScript) where you can define the <meta> tag directly in the HTML <head>.

For Next.js 13.4 and after (since you are using NextJS 14), with App Router / Migrating from pages to app, the traditional pages/_document.js and pages/_app.js files are replaced by a single root layout file located at app/layout.js (or layout.tsx if using TypeScript).
In this new structure, the root layout must explicitly define <html> and <body> tags, as Next.js no longer automatically generates them for pages within the app directory.

To manage elements in the <head>, such as setting meta tags including the viewport settings, Next.js now recommends using the built-in SEO support with the Metadata export in your root layout file.

The static viewport export or the dynamic generateViewport function allows you to configure viewport settings directly from your layout or page files, offering a more integrated and type-safe way to manage these settings in Next.js applications.
But... shrink-to-fit is not listed among the documented properties:

// Inside either a layout.tsx or page.tsx file
import type { Viewport } from 'next'

export const viewport: Viewport = {
  width: 500, // Custom width
  initialScale: 1,
  maximumScale: 1,
  userScalable: false
  // Note: Next.js does not explicitly support 'shrink-to-fit' in the Viewport type.
};

Since the direct inclusion of shrink-to-fit is not supported in the viewport object's type definition, one potential workaround would be to inject custom HTML into the head using a different method, such as a custom hook with next/head for dynamic cases, though this approach has its limitations and might not provide the type safety and integration benefits of the new viewport API.


Based on "How to use the meta viewport tag in NextJS 14 app router?", and the documentation "generateViewport":

Create the custom hook (useCustomViewport.js or useCustomViewport.ts):

// hooks/useCustomViewport.js
import { useEffect } from 'react';
import Head from 'next/head';

const useCustomViewport = () => {
  useEffect(() => {
    // Dynamically inject the viewport meta tag with 'shrink-to-fit=yes'
    const viewportMeta = document.querySelector("meta[name='viewport']");
    if (viewportMeta) {
      viewportMeta.content = 'width=500, shrink-to-fit=yes, maximum-scale=1, user-scalable=no';
    } else {
      // If not found, create and append the viewport meta tag
      const meta = document.createElement('meta');
      meta.name = 'viewport';
      meta.content = 'width=500, shrink-to-fit=yes, maximum-scale=1, user-scalable=no';
      document.getElementsByTagName('head')[0].appendChild(meta);
    }
  }, []);
};

export default useCustomViewport;

In your page or layout component, import and use the useCustomViewport hook to make sure the custom viewport settings are applied.

// pages/index.js or app/layout.js
import useCustomViewport from '../hooks/useCustomViewport';

export default function Home() {
  useCustomViewport(); // Use the custom hook to inject the viewport meta tag

  return (
    <div>
      {/* Your page or layout content */}
      <h1>Welcome to My Next.js App</h1>
    </div>
  );
}

By using this hook in your page or layout component, you dynamically inject the required viewport settings into your document. That makes sure your custom viewport settings, including properties like shrink-to-fit=yes, are respected across your Next.js application.


I'm already using a version similar to this, my issue with it is that it needs the layout.tsx to be a client component which prevents me from using NextJS Meta Tags among other things, and the page would need to be loaded first to take effect which results in the page being zoomed in then after a couple of seconds after it is fully loaded it zooms out which is not a good UX.
I'm looking for a solution that does not cause these issues, it does not necessarily have to use shrink-to-fit=yes

Ideally, you would need a solution that works within the server-side or static generation capabilities of Next.js, provided the viewport settings are correctly set before the page is rendered to the client to avoid the zoom-in and zoom-out issue.

But, since the direct setting of shrink-to-fit=yes is not supported in the Next.js 14 viewport API, and considering the limitations of using a client-side JavaScript approach, a server-side solution seems out of reach without native support for these specific viewport attributes.

A possible alternative to using shrink-to-fit=yes is to enhance the responsive design of the application to handle viewports narrower than 500px, without the need to scale the content.

Consider using CSS media queries and flexible layouts that adapt to various screen sizes, even those smaller than 500px. That would eliminate the need for the page to scale down on smaller devices, and... bypass the initial problem.

/* Base styles */
body, html {
  margin: 0;
  padding: 0;
}

.container {
  min-width: 500px; /* Use min-width to make sure content has a base width */
}

/* Responsive styles for screens narrower than 500px */
@media (max-width: 499px) {
  .container {
    min-width: auto; /* Allow the container to shrink on small devices */
    width: 100%; /* Use the full width of the screen */
    padding: 10px; /* Add some padding for smaller screens */
  }

  /* Adjust font sizes, image sizes, etc., as needed */
  .text {
    font-size: 14px;
  }
}

A CSS flexbox or grid is used to create layouts that can adapt to various screen sizes without specifying a fixed width that would require scaling. Media queries apply different styles based on the viewport width, making sure that content remains accessible and legible on smaller screens. Use relative units (e.g., em, rem, %, vw, vh) for widths, font sizes, and spacing, rather than fixed pixels, to allow for more fluid scaling across devices. Make sure images and embedded media are responsive, using CSS techniques or HTML attributes (width="100%", height="auto") to make them scale appropriately.

That should provide a way to make sure your application is accessible and usable across all device sizes, including those narrower than 500px, without the adverse effects of manual scaling or client-side dynamic adjustments.