Fix: Missing viewport meta
Missing a viewport meta tag forces mobile browsers to render your site at a desktop width (typically 980px in iOS Safari and Chrome for Android) and scale it down. Text becomes illegible, tap targets shrink below the 48x48 pixel accessibility standard, and users must pinch-to-zoom to interact with your UI. Google enforces mobile-first indexing for 100% of websites. When Googlebot Smartphone crawls your page, it executes JavaScript and renders the DOM. If it detects a layout wider than the mobile viewport without a scaling directive, it flags the page as not mobile-friendly in Google Search Console. Pages failing the mobile-friendly test receive a direct ranking demotion for mobile search queries. Indxel tracks this failure via the viewport-meta rule, assigning it a severity weight of 3/100. Modern frameworks like Next.js inject this tag automatically. However, custom _document.tsx overrides, static HTML exports, or migrating from Next.js 13 to Next.js 14 without refactoring the metadata object can silently drop the tag from your production build. Fixing this issue adds zero bytes to your JavaScript bundle and instantly restores proper mobile layout rendering.
How do you detect missing viewport tags?
Run npx indxel check to scan your local build or production URL for the viewport-meta rule. The CLI flags pages lacking the standard width=device-width declaration.
The CLI outputs warnings in the same format as ESLint — one line per issue, with file path and rule ID.
The viewport-meta rule has a severity of WARNING and reduces your page's Indxel score by 3 points (Weight: 3/100).
$ npx indxel check .next/server/app
Checking 47 pages...
/marketing/landing-page
⚠ viewport-meta: Missing <meta name="viewport"> tag.
↳ Fix: Export a viewport object or add the tag to your layout.
✖ 1 issue found (0 errors, 1 warning)
Score: 97/100How do you fix a missing viewport meta tag?
Add the <meta name="viewport" content="width=device-width, initial-scale=1" /> tag to your document head. In Next.js App Router, export the viewport object from your root layout.
Next.js App Router (Next.js 14+)
Next.js App Router separates viewport configuration from general metadata. If you previously defined viewport inside the metadata object (a pattern deprecated in Next.js 14), Next.js fails silently and omits the tag from your compiled HTML.
You must export a separate Viewport object.
// app/layout.tsx
// ❌ BAD: Viewport inside metadata is ignored in Next.js 14+
export const metadata: Metadata = {
title: 'API Documentation',
viewport: 'width=device-width, initial-scale=1', // Ignored completely
};
// ✅ GOOD: Export a separate Viewport object
import type { Viewport, Metadata } from 'next';
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1, // Optional: prevents zooming on form focus in iOS Safari
};
export const metadata: Metadata = {
title: 'API Documentation',
};Next.js Pages Router
In the Next.js Pages router, you must add the meta tag to your custom _document.tsx or _app.tsx file. Injecting it in _app.tsx inside the next/head component is the Vercel-recommended pattern. Placing it in _document.tsx can cause React hydration mismatch errors when using certain CSS-in-JS libraries.
// pages/_app.tsx
import Head from 'next/head';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Head>
{/* ✅ GOOD: Added globally in _app.tsx */}
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<Component {...pageProps} />
</>
);
}Vite / React Single Page Applications
If you are using Vite to build a standard React SPA, the viewport tag belongs in your static index.html file. Vite does not parse React components to generate the static head. You must place the tag directly inside the <head> element, immediately following the charset declaration.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<!-- ✅ GOOD: Hardcoded in the static entry file -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Internal Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>Indxel SDK
If you manage SEO centrally using the Indxel SDK, define the viewport in your global configuration. The defineSEO function types the viewport object strictly, catching typos at compile time before they reach the browser.
// lib/seo.ts
import { defineSEO } from '@indxel/core';
export const globalSEO = defineSEO({
title: {
default: 'Acme Corp',
template: '%s | Acme Corp',
},
// ✅ GOOD: Indxel SDK handles the tag injection automatically
viewport: {
width: 'device-width',
initialScale: 1,
}
});How do you prevent missing viewports in CI?
Add npx indxel check --ci to your build pipeline. This command fails the build if any page drops the viewport tag, guaranteeing unresponsive pages never reach production.
Because the viewport-meta rule is classified as a warning, it will not fail a standard Indxel run by default. You must configure Indxel to fail on warnings or enforce a strict score threshold in your CI environment.
We recommend setting a minimum score threshold of 95 in your indxel.config.ts. This catches cumulative warnings (like 4 pages missing viewports and 2 missing favicons) without failing the build on a single minor infraction.
Vercel Build Command
Override your Vercel build command to run the Indxel check immediately after generating static pages. If the check fails, Vercel aborts the deployment.
# Vercel Project Settings > Build & Development Settings
npm run build && npx indxel check .next/server/app --min-score 95GitHub Actions Validation
For pull request validation, run Indxel against your built application. The --diff flag ensures you only test pages modified in the current branch, keeping CI runtimes under 10 seconds.
# .github/workflows/seo-guard.yml
name: SEO Guard
on: [pull_request]
jobs:
validate-seo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
- name: Run Indxel
# Fails the PR check if the score drops below 95
run: npx indxel check .next/server/app --ci --min-score 95 --diffWhat are common edge cases for viewport meta tags?
You can inject a viewport tag and still fail mobile usability checks if the content string is malformed or if you misuse scaling directives.
CSS Media Queries Failing
If your viewport tag is missing, @media (max-width: 768px) will never trigger on a mobile device. Because the mobile browser defaults to a 980px virtual canvas, it evaluates the media query against 980px, not the physical 390px width of an iPhone screen. If you write responsive Tailwind classes (md:flex, lg:grid) but see the desktop layout on your phone, the missing viewport tag is the culprit 100% of the time.
Maximum-scale and Accessibility Violations
Using maximum-scale=1.0 or user-scalable=no prevents users from pinching to zoom. Developers often use this to stop iOS Safari from auto-zooming when a user taps an <input> field. However, this creates severe accessibility issues. The WCAG 2.1 guidelines mandate that text can be resized up to 200% without assistive technology.
If you use user-scalable=no, Chrome Lighthouse audits will fail your accessibility score, dropping it by 10 points. Fix the iOS input zoom issue by setting your base input font sizes to exactly 16px, rather than disabling zoom globally.
// ❌ BAD: Breaks WCAG accessibility guidelines
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1,
userScalable: false, // Fails Lighthouse accessibility audit
};
// ✅ GOOD: Accessible responsive viewport
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
};Iframe Isolation
If your application embeds third-party widgets or iframes (like a Stripe checkout instance or an Intercom chat window), those iframes must contain their own viewport meta tags. Indxel only scans the parent document DOM. If the HTML loaded inside an iframe lacks a viewport tag, the content inside that specific iframe renders at desktop width, causing horizontal scrolling within the iframe container.
Multiple Viewport Declarations
If you mix Next.js metadata exports and custom <Head> injections in the same route tree, you will render two viewport tags. Browsers parse the last one they encounter in the DOM sequence. Indxel flags duplicate viewport tags under a separate rule (duplicate-meta), but you should ensure your root layout acts as the single source of truth for viewport configuration.
Related rules
Missing viewport tags frequently correlate with other missing global <head> elements. Indxel tracks these via:
missing-favicon: Verifies the presence of a valid<link rel="icon">returning a 200 HTTP status.missing-title: Ensures every page has a<title>tag between 30 and 60 characters.duplicate-meta: Catches instances where manual tag injection conflicts with framework-generated metadata.
Does Next.js add the viewport automatically?
Yes. Next.js App Router automatically adds the viewport meta tag to all pages. You will only see the viewport-meta error if you explicitly override the default layout, misuse the deprecated Next.js 13 metadata object, or use a custom _document.tsx in the Pages router.
Does the viewport tag affect desktop SEO?
No. The viewport meta tag only instructs mobile browsers on how to scale the page. Desktop browsers ignore the device-width directive entirely, and Googlebot's desktop crawler does not use it for ranking signals. It strictly impacts mobile search rankings.
Can I set the viewport dynamically per route?
Yes. You can export a different viewport object from any page.tsx or layout.tsx file in Next.js. The deeply nested route's export will overwrite the root layout's viewport configuration for that specific path.
Why does Google Search Console say "Text too small to read"?
This error triggers when your font size is under 12px or when the viewport meta tag is missing. Without the viewport tag, Google renders the page at 980px width and shrinks it to fit a 360px mobile screen, mathematically reducing a standard 16px font to a microscopic 5.8px.
Frequently asked questions
Does Next.js add the viewport automatically?
Yes. Next.js App Router automatically adds the viewport meta tag. You should almost never see this error in a Next.js project unless you've overridden the default behavior.