Every Nuxt.js fix we catalog

SEOLZ catalogs 18 fixes for Nuxt.js across 3 areas — SEO, answer-engine readiness, accessibility, security and site-health. Each lists the exact steps for Nuxt.js, with a link to the official docs.

SEO · 12 fixes

Duplicate meta descriptionModerate effort

Write a unique, page-specific meta description for every page on your store so Google can display a compelling, accurate snippet for each one in search results.

On Nuxt.js

  1. Install / confirm you are on Nuxt 3+ and have access to `useSeoMeta` or `useHead` composables — no extra packages needed, they are auto-imported.
  2. Remove any global, static `description` set in `nuxt.config.ts` under `app.head.meta` (or move it to a safe fallback only for pages that genuinely have no description yet).
  3. In every page component (`pages/*.vue`, e.g. `pages/index.vue`, `pages/products/[handle].vue`, `pages/collections/[slug].vue`), call `useSeoMeta()` at the top of `<script setup>` with a description derived from that page's own data (fetched product/collection/CMS copy).
  4. For dynamic routes (PDP, PLP, blog posts), fetch the page data first (`useAsyncData` / `useFetch`), then pass reactive computed values into `useSeoMeta` so the description updates with the data — Nuxt will SSR the correct tag automatically.
  5. Enforce a character budget: truncate descriptions to 150–160 characters so Google does not trim them unpredictably. Add a shared `truncate(str, 155)` utility in `utils/seo.ts` and reuse it across all pages.
  6. Validate the output with `nuxi generate` + a quick `grep '<meta name="description"'` on the built HTML, or use the Chrome DevTools / Google Search Console URL Inspection tool to confirm each page has a unique, non-empty description tag.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <meta name="description" content="Shop women's running shoes at Acme Store — lightweight, cushioned designs for road and trail. Free shipping on orders over $50.">
  <title>Women's Running Shoes | Acme Store</title>
</head>

Place inside the <head> element of every page. Replace the content value with a unique 120–160 character description for that specific page.

Duplicate titleModerate effort

Give every page a unique, descriptive title tag that accurately reflects that page's specific content.

On Nuxt.js

  1. Set a global default title template in `nuxt.config.ts` using the `app.head.titleTemplate` option (e.g. `'%s | My Store'`). This ensures every page that sets its own title automatically gets the store suffix appended, making every title unique by construction.
  2. Remove any hard-coded, shared `<title>` or `useHead({ title: '…' })` calls that are duplicated across pages — typically found in `app.vue`, `layouts/default.vue`, or shared composables.
  3. In every page component (`pages/index.vue`, `pages/products/[slug].vue`, `pages/categories/[slug].vue`, `pages/cart.vue`, etc.) call `useSeoMeta({ title: … })` (Nuxt 3) with a value derived from the page's own dynamic data (product name, category name, etc.) so the resulting title is unique to that page.
  4. For dynamic routes (e.g. `pages/products/[slug].vue`) fetch the resource first (via `useAsyncData` / `useFetch`), then pass the fetched entity's name into `useSeoMeta` so the title reflects real content rather than a generic placeholder.
  5. For the root page (`pages/index.vue`) pass an explicit `titleTemplate` override of `'%s'` (or a fully written-out string) so the homepage title does NOT get the suffix appended twice or left as a bare template string.
  6. Audit remaining pages (account, checkout, 404, search) and ensure each one calls `useSeoMeta({ title: … })` with a unique, human-readable string — never leaving the title undefined, empty, or identical to another page.
Code · Generic HTML (html)
<head>
  <title>Merino Wool Running Socks – Free UK Shipping | BrandName</title>
</head>

Place inside <head>. Make the title unique per page — include the specific item name and brand.

Images missing altModerate effort

Add descriptive alt text to every product and content image so search engines and screen readers can understand what the image shows.

On Nuxt.js

  1. Audit every `<img>` tag and `<NuxtImg>` / `<NuxtPicture>` component across your pages and components (e.g. `pages/products/[slug].vue`, `components/ProductCard.vue`, `components/HeroBanner.vue`) and ensure each one has a non-empty, descriptive `alt` prop.
  2. For product images, derive the `alt` string dynamically from your API/store data — prefer a dedicated `altText` field if the commerce API exposes one (e.g. Shopify Storefront API `image.altText`), otherwise fall back to the product title plus a context hint (e.g. `'${product.title} – front view'`).
  3. For decorative images that carry no semantic meaning (spacers, background textures), pass an explicit empty string `alt=""` so screen readers and crawlers correctly skip them — never omit the attribute entirely.
  4. Create a reusable `BaseImage.vue` component that enforces the `alt` prop as required (using Vue's `required: true` prop validation). Use this component everywhere instead of raw `<img>` tags so missing alt text causes a console warning during development.
  5. If you use `@nuxt/image`, replace all raw `<img>` tags with `<NuxtImg>` and wire the `alt` prop — the module passes it straight through to the rendered `<img>`, so no extra config is needed.
  6. Run `nuxi build` (or check the browser console in `nuxi dev`) to catch any prop-validation warnings for missing `alt` values before deploying.
Code · Generic HTML (html)
<!-- BEFORE (missing alt — bad) -->
<img src="/images/red-leather-bag.jpg">

<!-- AFTER (descriptive alt — correct) -->
<img src="/images/red-leather-bag.jpg" alt="Red leather crossbody bag with gold buckle clasp">

<!-- Decorative image (intentionally empty alt — correct) -->
<img src="/images/divider-line.png" alt="">

Place this wherever an <img> tag appears in your HTML. Replace the placeholder text with your real description.

Meta description too longQuick win

Shorten your meta description to 150–160 characters so Google displays it in full instead of cutting it off with "…" in search results.

On Nuxt.js

  1. Locate every page component (e.g. pages/index.vue, pages/products/[slug].vue, pages/categories/[slug].vue) and any shared SEO composable that sets the meta description.
  2. Replace raw, hard-coded description strings with a truncation utility (truncateDesc) that enforces a hard cap of 155 characters, preserving whole words and appending '…' only when the source string exceeds the limit.
  3. Pass the sanitised description to useSeoMeta({ description }) (preferred — Nuxt 3) or useHead({ meta: [{ name: 'description', content }] }) in every page where meta is set.
  4. For dynamic pages that fetch product/category data (e.g. via useAsyncData or useFetch), call truncateDesc on the API-returned description field before handing it to useSeoMeta so CMS copy that is too long is automatically capped.
  5. Add a composable file (composables/useTruncateDesc.ts) so the helper is auto-imported project-wide — no manual import required in any page.
  6. Validate the output length in your CI pipeline or with Nuxt DevTools by inspecting the rendered <meta name='description'> tag and confirming it is ≤ 160 characters.
Code · Generic HTML (html)
<meta name="description" content="Award-winning Bang & Olufsen headphones, speakers and TVs. Visit our Malaysia showroom at The Gardens Mall, Level 2. Free shipping on orders over RM500.">

Place this inside the <head> element of every page. Replace the content value with your page-specific description (max 160 characters).

Meta description too shortModerate effort

Expand your meta description to 120–160 characters so Google displays a compelling, complete snippet in search results that drives more clicks to your store.

On Nuxt.js

  1. Install / confirm you are using Nuxt 3's built-in `useSeoMeta` composable (no extra packages needed) — it is the recommended, type-safe way to set every meta tag including `description`.
  2. Identify every page component under `pages/` (e.g. `pages/products/[slug].vue`, `pages/index.vue`, `pages/collections/[handle].vue`) that currently has a missing or short meta description.
  3. In each page component, call `useSeoMeta()` inside `<script setup>` and supply a `description` string of 120–160 characters. For dynamic pages (PDP, PLP) fetch the product/collection data first, then derive the description from real content (name + key benefit + CTA) so it is unique per URL.
  4. For site-wide fallback defaults, open (or create) `app.vue` and call `useSeoMeta()` there with a generic 120–160-character description; per-page calls will override it automatically thanks to Nuxt's meta-merging.
  5. Run `nuxt build && nuxt preview` (or `nuxt generate` for SSG) and inspect the rendered HTML `<head>` with `curl` or DevTools → View Page Source to confirm the `<meta name="description">` tag is present and between 120–160 characters on every key URL.
  6. Optionally add a lint/test step (e.g. a Vitest unit test or a Playwright check) that reads `document.querySelector('meta[name="description"]').content.length` and asserts it is `>= 120 && <= 160` so regressions are caught in CI.
Code · Nuxt.js (javascript)
// Nuxt 3 — inside <script setup>
useHead({
  meta: [
    {
      name: 'description',
      content:
        'Shop the official Nike national team jerseys — advanced Dri-FIT heat tech meets bold heritage design. Free shipping on orders over $75. Explore the full collection now.',
    },
  ],
});

Add this inside your page's <script setup> block (Nuxt 3) or head() function (Nuxt 2).

Missing h1Quick win

Add a single, descriptive H1 tag to every page that currently lacks one, clearly stating the page's main topic.

On Nuxt.js

  1. Audit every page component under `pages/` (e.g. `pages/index.vue`, `pages/products/[slug].vue`, `pages/categories/[slug].vue`, `pages/cart.vue`, etc.) and identify any that are missing a visible `<h1>` element in their `<template>`.
  2. For each affected page, derive a meaningful H1 string from the page's data: use the fetched product title, category name, or a static label (e.g. 'Shopping Cart'). Store it in a `computed` property or a `ref` so it stays reactive to async data.
  3. Add exactly ONE `<h1>` element as the first meaningful heading inside the page's `<template>`. Do NOT place an H1 inside a shared layout (`layouts/default.vue`) unless every layout consumer genuinely shares the same heading — put it in the individual page component.
  4. Keep the H1 text consistent with the page's `<title>` tag (set via `useSeoMeta` or `useHead`). Reuse the same reactive variable for both so they never drift apart.
  5. For pages that load data asynchronously with `useAsyncData` / `useFetch`, guard against a blank flash by providing a sensible fallback string (e.g. `product.value?.title ?? 'Loading…'`) and ensure the `<h1>` is still present in the server-rendered HTML for crawlers.
  6. Run `nuxt build && nuxt preview` (or check the SSR response with `curl`) to confirm the `<h1>` appears in the raw HTML before any client-side JavaScript executes.
Code · Generic HTML (html)
<h1>Women's Running Shoes</h1>

Place exactly one of these per page, as the first heading in the main content area.

Missing meta descriptionModerate effort

Write and add a unique meta description (120–160 characters) to every page that is missing one, so Google can display an accurate, compelling snippet in search results.

On Nuxt.js

  1. In a page component, use the `useHead` composable: `useHead({ meta: [{ name: 'description', content: 'Your unique description here.' }] })`.
  2. For a global site default, set `app.head.meta` in `nuxt.config.ts`: `app: { head: { meta: [{ name: 'description', content: 'Site-wide default description.' }] } }`.
  3. For dynamic pages (products, etc.), fetch the description from your CMS in `asyncData` or a `useFetch` call and pass it to `useHead` or `useSeoMeta`.
Code · Nuxt.js (javascript)
<script setup>
const { data: product } = await useFetch(`/api/products/${useRoute().params.slug}`);

useSeoMeta({
  description: product.value.seoDescription, // 120–160 chars
});
</script>

<template>
  <main><!-- page content --></main>
</template>

Vue 3 / Nuxt 3 page component using useSeoMeta composable.

Missing og descriptionModerate effort

Add an og:description meta tag to every page so that social media platforms display a compelling, accurate preview when your pages are shared.

On Nuxt.js

  1. Set a site-wide fallback og:description in nuxt.config.ts under app.head.meta so every page has a baseline Open Graph description even before page-level overrides are applied.
  2. On each page component (e.g. pages/index.vue, pages/products/[slug].vue), call useSeoMeta() inside <script setup> and pass ogDescription with a value derived from the page's dynamic data (product description, category blurb, CMS field, etc.).
  3. Fetch the page-specific description from your headless backend (e.g. Shopify Storefront API, Contentful, Sanity) before passing it to useSeoMeta(), using useAsyncData() or useFetch() so the tag is available during SSR.
  4. For a shared layout pattern, create a composable (e.g. composables/useSeoDefaults.ts) that accepts a description string, applies truncation to ≤ 160 characters, and calls useSeoMeta() — then reuse it across every page.
  5. Verify the tag is rendered server-side by running `nuxi build && nuxi preview` and inspecting the raw HTML with `curl` or browser DevTools → View Page Source; confirm <meta property="og:description" …> is present in the initial HTML payload.
  6. Optionally audit all routes with a Lighthouse CI or a crawl script to assert no page returns a missing og:description, and add it to your CI pipeline.
Code · Nuxt.js (javascript)
<script setup>
// pages/products/[slug].vue (Nuxt 3)
const route = useRoute();
const { data: product } = await useAsyncData('product', () =>
  $fetch(`/api/products/${route.params.slug}`)
);

useSeoMeta({
  ogTitle: () => product.value?.name,
  ogDescription: () => product.value?.shortDescription, // 100-160 chars
  ogImage: () => product.value?.imageUrl,
  ogUrl: () => `https://www.example.com/products/${route.params.slug}`,
});
</script>

Use the useSeoMeta composable in your page component's <script setup> block.

Missing og imageModerate effort

Add an og:image meta tag to every page so that social media platforms and messaging apps display a rich, branded image preview whenever someone shares your store's link.

On Nuxt.js

  1. Set a global fallback og:image in nuxt.config.ts using the `app.head` key so every page inherits it automatically — this covers pages that don't set their own image.
  2. Override the og:image on product, collection, and content pages by calling `useSeoMeta()` inside each page's `<script setup>` block, passing the page-specific image URL fetched from your headless commerce API (e.g. Shopify Storefront API `product.featuredImage.url`).
  3. For dynamic routes (e.g. pages/products/[handle].vue), derive the og:image from the fetched data and pass it reactively using a computed ref or directly as a dynamic string to `useSeoMeta()`.
  4. Ensure every og:image URL is absolute (starts with https://), is at least 1200×630 px, and is publicly accessible without authentication — use `useRuntimeConfig().public.baseUrl` or a CDN prefix to build absolute URLs if your image paths are relative.
  5. Add the complementary `og:image:width`, `og:image:height`, and `og:image:alt` properties alongside `og:image` for full Open Graph compliance and better social previews.
  6. Validate the output with the Facebook Sharing Debugger and the LinkedIn Post Inspector after deploying, then confirm the tags appear in the SSR-rendered HTML source (not just in the client-side DOM).
Seo facet url not canonicalizedModerate effort

Add a canonical tag to every filtered, sorted, and paginated collection/category URL pointing back to the clean (unfiltered) category URL, so Google consolidates duplicate pages and focuses crawl budget on your real content.

On Nuxt.js

  1. In your category page component or layout, use the useHead composable (Nuxt 3) to set the canonical dynamically based on the route path, excluding query params: useHead({ link: [{ rel: 'canonical', href: `https://www.example.com${route.path}` }] });
  2. This uses route.path (clean path only) rather than route.fullPath (which includes query strings).
  3. For Nuxt 2 with vue-meta, use head() { return { link: [{ rel: 'canonical', href: `https://www.example.com${this.$route.path}` }] }; }.
  4. Verify by building and checking page source on a filtered URL.
Code · Nuxt.js (vue)
<script setup lang="ts">
import { useRoute, useHead } from '#imports';

const route = useRoute();
const canonicalUrl = `https://www.example.com${route.path}`;

useHead({
  link: [
    {
      rel: 'canonical',
      href: canonicalUrl,
    },
  ],
});
</script>

<template>
  <main>
    <!-- page content -->
  </main>
</template>

Nuxt 3: place this in pages/collections/[slug].vue. Uses route.path (no query string) for the canonical.

Title too longQuick win

Shorten your page title tag to 60 characters or fewer so it displays fully — without truncation — in Google search results.

On Nuxt.js

  1. Create a reusable title-truncation utility (e.g. utils/seo.ts) that hard-caps any title at 60 characters, appending an ellipsis only when the raw string exceeds the limit.
  2. In every page component (pages/*.vue) or layout that sets a title, replace the raw title string with the truncated version produced by that utility.
  3. Use useSeoMeta() (preferred in Nuxt 3) or useHead() to set the processed title — both are auto-imported composables; no import statement is needed.
  4. For site-wide / fallback titles defined in nuxt.config.ts (app.head.title / app.head.titleTemplate), apply the same 60-character rule directly in the config string.
  5. After updating, run `nuxi build && nuxi preview` and inspect <title> in page source, or use a browser extension, to confirm no title exceeds 60 characters.
  6. Optionally add a CI lint rule or a Vitest unit test that calls truncateTitle() with a >60-char string and asserts the output length is ≤ 60.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <title>Running Shoes for Men | YourBrand</title>
</head>

Place inside the <head> element of every HTML page. Keep the total character count at or under 60.

Title too shortQuick win

Expand your page title to between 30 and 60 characters so Google displays it fully in search results and shoppers can immediately understand what the page is about.

On Nuxt.js

  1. Identify every page component (e.g. pages/index.vue, pages/products/[slug].vue, pages/collections/[handle].vue) whose <title> is currently under 50 characters.
  2. Use Nuxt's useSeoMeta() composable (preferred in Nuxt 3) inside the <script setup> block of each page to set the title. Aim for 50–60 characters, including the brand name appended with a separator (e.g. ' | Brand Name').
  3. For dynamic pages (PDPs, PLPs), fetch the product/collection name from your headless commerce API first, then interpolate it into a computed title string before passing it to useSeoMeta(). Use a computed() or a plain template-literal that combines the resource name + descriptor + brand.
  4. Add a fallback default title in nuxt.config.ts under app.head.title (and app.head.titleTemplate) so any page that forgets its own title still meets the 50-60 character target.
  5. Validate character length at runtime with a thin helper (title.length >= 50 && title.length <= 60) and log a console.warn in development so future pages are caught early.
  6. After deploying, confirm rendered <title> tags in View Source (not DevTools) to ensure SSR is outputting the full title before Google crawls the page.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <title>Men's Merino Wool Beanies – BrandName</title>
</head>

Place inside the <head> element of every page. Replace the example with your actual descriptive title (30–60 chars).

Answer Engine Optimization · 3 fixes

Missing schema breadcrumblistModerate effort

Add BreadcrumbList structured data (JSON-LD) to every page so Google can display your site's navigation path directly in search results.

On Nuxt.js

  1. Create a reusable composable at `composables/useBreadcrumbSchema.ts` that accepts a breadcrumb array and returns a fully-formed BreadcrumbList JSON-LD object.
  2. In every page component (e.g. `pages/products/[slug].vue`, `pages/categories/[slug].vue`, `pages/index.vue`), import the composable, build the breadcrumb array from the page's data/route, then inject the JSON-LD into the `<head>` via Nuxt's `useHead()` composable using a `<script type='application/ld+json'>` tag.
  3. For deeply nested or shared layouts (e.g. `layouts/default.vue`), consider passing breadcrumb data down via `useState` or props so the JSON-LD is always page-specific and not duplicated.
  4. Ensure each `ListItem` uses a fully-qualified absolute URL (e.g. `https://example.com/category/shoes`) — use `useRuntimeConfig().public.siteUrl` or a composable to resolve the base URL dynamically.
  5. After deploying, validate every key page type (home, category, PDP) with Google's Rich Results Test and Schema.org's validator to confirm BreadcrumbList is parsed correctly.
Code · Generic HTML (json)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://www.example.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Women's Clothing",
      "item": "https://www.example.com/womens-clothing/"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Dresses",
      "item": "https://www.example.com/womens-clothing/dresses/"
    },
    {
      "@type": "ListItem",
      "position": 4,
      "name": "Blue Wrap Dress",
      "item": "https://www.example.com/womens-clothing/dresses/blue-wrap-dress/"
    }
  ]
}
</script>

Place this <script> block inside <head> or anywhere in <body> on any page with a breadcrumb trail. Extend itemListElement with one ListItem per level.

Missing schema organizationQuick win

Add an Organization schema markup block to your store's homepage (and/or sitewide template) so search engines and AI assistants can definitively identify your brand.

On Nuxt.js

  1. Open (or create) your homepage component — typically `pages/index.vue` — in your Nuxt.js headless storefront.
  2. Inside the `<script setup>` block, use Nuxt's `useHead()` composable to inject a `<script type='application/ld+json'>` tag containing the Organization schema. This keeps all SEO logic in one composable and is SSR-rendered on first load.
  3. Populate every Organization field: `@context`, `@type`, `name`, `url`, `logo` (as an ImageObject), `sameAs` (array of social profile URLs), and optionally `contactPoint` for customer-service markup.
  4. Replace all placeholder values (`YOUR_STORE_NAME`, `https://yourdomain.com`, logo URL, and social URLs) with real data — ideally sourced from a runtime config (`useRuntimeConfig()`) or a CMS/API fetch so the values stay maintainable.
  5. Run `nuxt dev` and inspect the page source (View Source, not DevTools Elements) to confirm the raw `<script type='application/ld+json'>` tag is present in the server-rendered HTML.
  6. Validate the output by pasting the page URL (or the JSON blob) into Google's Rich Results Test (https://search.google.com/test/rich-results) and Schema.org's validator (https://validator.schema.org/) before deploying.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Acme Store",
  "url": "https://www.acmestore.com",
  "logo": {
    "@type": "ImageObject",
    "url": "https://www.acmestore.com/images/logo.png",
    "width": 250,
    "height": 250
  },
  "sameAs": [
    "https://www.facebook.com/acmestore",
    "https://www.instagram.com/acmestore",
    "https://twitter.com/acmestore",
    "https://www.linkedin.com/company/acmestore",
    "https://www.youtube.com/c/acmestore"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "contactType": "customer service",
    "email": "hello@acmestore.com"
  }
}
</script>

Drop this complete block inside your <head>…</head> section on every page. Replace all placeholder values.

Missing schema webpageModerate effort

Add WebPage schema markup to every page so search engines and AI answer engines can precisely understand and describe your content.

On Nuxt.js

  1. Create a reusable composable `composables/useSchemaOrg.ts` that accepts page-level overrides and returns both a `WebPage` and `Organization` JSON-LD object, sourcing site-wide values (name, URL, logo) from `useRuntimeConfig` / `app.config.ts`.
  2. In `app.config.ts` (or `nuxt.config.ts` → `runtimeConfig.public`), add your site-wide Organisation fields: `siteName`, `siteUrl`, `logoUrl`, `socialProfiles[]` so they are available everywhere without hard-coding.
  3. In every page component (e.g. `pages/index.vue`, `pages/products/[slug].vue`), call `useSchemaOrg()` with page-specific values (`name`, `description`, `url`, `breadcrumb`, `dateModified`, etc.) and inject the result into `<head>` via Nuxt's `useHead()` composable as a `<script type='application/ld+json'>` tag.
  4. For dynamic routes (PDPs, PLPs, blog posts), forward the SSR-fetched product/collection data directly into `useSchemaOrg()` inside the same `<script setup>` block so the JSON-LD is rendered server-side and present in the initial HTML.
  5. Use Nuxt Devtools (or `curl` + `grep 'application/ld+json'`) to verify the tags appear in the SSR-rendered HTML, then validate the output with Google's Rich Results Test and Schema.org validator.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "WebPage",
  "name": "About Us | Acme Store",
  "description": "Learn about Acme Store, our mission, and the team behind our products.",
  "url": "https://www.example.com/about/",
  "inLanguage": "en-US",
  "datePublished": "2022-01-15",
  "dateModified": "2024-06-01",
  "isPartOf": {
    "@type": "WebSite",
    "name": "Acme Store",
    "url": "https://www.example.com/"
  },
  "breadcrumb": {
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Home",
        "item": "https://www.example.com/"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "About Us",
        "item": "https://www.example.com/about/"
      }
    ]
  }
}
</script>

Paste this inside the <head> element of any HTML page. Replace all placeholder values with real page data.

Security (OWASP) · 3 fixes

Info disclosure x powered byQuick win

Remove or suppress the X-Powered-By HTTP response header so your server stops advertising which technology stack, hosting platform, or software version powers your store.

On Nuxt.js

  1. For Nuxt 3 (Nitro server): create or edit server/middleware/headers.ts and export default defineEventHandler((event) => { removeResponseHeader(event, 'x-powered-by'); });
  2. For Nuxt 2 with Express: add app.disable('x-powered-by'); in your server middleware and use the helmet package.
  3. Redeploy and verify.
Code · Nuxt.js (Nuxt 3 / Nitro) (typescript)
// server/middleware/remove-headers.ts
export default defineEventHandler((event) => {
  // Remove X-Powered-By header from all responses
  removeResponseHeader(event, 'x-powered-by');
});

Create the file server/middleware/remove-headers.ts in your Nuxt 3 project root.

Vulnerable component axiosModerate effort

Update the Axios JavaScript library to its latest stable release to eliminate known security vulnerabilities in your storefront code.

On Nuxt.js

  1. In your Nuxt project root, run `npm install axios@latest` (or update @nuxtjs/axios if you use that module, via `npm install @nuxtjs/axios@latest`).
  2. Rebuild with `npm run build` and redeploy.
  3. Run `npm audit` to confirm no remaining Axios advisories.
  4. If using Nuxt 3 with native $fetch, consider migrating away from Axios entirely as $fetch is built-in and maintained by the Nuxt core team.
Code · Generic HTML (html)
<!-- Load the latest Axios from jsDelivr CDN (always points to newest stable release) -->
<script src="https://cdn.jsdelivr.net/npm/axios@latest/dist/axios.min.js" integrity="" crossorigin="anonymous"></script>

<!-- OR pin to a specific safe version (recommended for production stability) -->
<script src="https://cdn.jsdelivr.net/npm/axios@1.7.9/dist/axios.min.js" crossorigin="anonymous"></script>

Replace any hardcoded Axios CDN script tag in your HTML templates with this — always uses the latest stable version.

X frame options weakQuick win

Replace the weak X-Frame-Options header value with DENY or SAMEORIGIN to prevent clickjacking attacks on your store.

On Nuxt.js

  1. Install the nuxt-security module: npm install nuxt-security
  2. In nuxt.config.ts add: security: { headers: { xFrameOptions: 'DENY' } }
  3. Rebuild and redeploy; verify the header in DevTools.
Code · Generic HTML (html)
<!-- X-Frame-Options MUST be sent as an HTTP response header, not a meta tag. -->
<!-- Use the server-side or platform snippets below instead. -->

<!-- The modern equivalent you CAN set via meta tag (limited support): -->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none';">

If you cannot set server-level headers, use this meta-equivalent only as a last resort — note that X-Frame-Options via meta tag is NOT supported by browsers (it must be an HTTP header). This snippet is shown for illustration only; use server-side headers.