Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change Site Theme
Sanity logo

Documentation

    • Overview
    • Platform introduction
    • Next.js quickstart
    • Nuxt.js quickstart
    • Astro quickstart
    • React Router quickstart
    • Studio quickstart
    • Build with AI
    • Content Lake
    • Functions
    • APIs and SDKs
    • Agent Actions
    • Visual Editing
    • Blueprints
    • Platform management
    • Dashboard
    • Studio
    • Canvas
    • Media Library
    • App SDK
    • Content Agent
    • HTTP API
    • CLI
    • Libraries
    • Specifications
    • Changelog
    • User guides
    • Developer guides
    • Courses and certifications
    • Join the community
    • Templates
Nuxt.js quickstart

  • Quickstart guide

    Setting up your studio
    Defining a schema
    Querying content with GROQ
    Displaying content in Nuxt.js
    Deploying Studio and inviting editors

  • ← Back to all getting started guides

On this page

Previous

Querying content with GROQ

Next

Deploying Studio and inviting editors

Was this page helpful?

On this page

  • Install a new Nuxt application
  • Install Sanity dependencies
  • Configure the Sanity client
  • Start the development server
  • Display content on the home page
  • Display individual posts
Nuxt.js quickstartLast updated February 2, 2026

Displaying content in a Nuxt.js front end

You’ve configured your Studio with a post document type and learned how to query from your hosted dataset. Before deploying the Studio, let’s query and display this content on the front-end framework of your choice.

1Install a new Nuxt application

If you have an existing application, skip this first step and adapt the rest of the lesson to install Sanity dependencies to fetch and render content.

Run the following in a new tab or window in your Terminal (keep the Studio running) to create a new Nuxt application using the Nuxt UI template for Tailwind CSS.

You should now have your Studio and Nuxt application in two separate, adjacent folders:

# outside your studio directory
npx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
pnpm dlx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
yarn dlx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
bunx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
├─ /nuxt-hello-world
└─ /studio-hello-world

2Install Sanity dependencies

Run the following inside the nuxt-hello-world directory to:

  • Install and configure the Nuxt Sanity integration
  • Install @sanity/image-url for generating images from Sanity content
npx nuxi@latest module add sanity
npm install @sanity/image-url
pnpm dlx nuxi@latest module add sanity
pnpm add @sanity/image-url
yarn dlx nuxi@latest module add sanity
yarn add @sanity/image-url
bunx nuxi@latest module add sanity
bun add @sanity/image-url

3Configure the Sanity client

Update the integration configuration to configure a Sanity Client to fetch content.

export default defineNuxtConfig({
  compatibilityDate: "2024-04-03",
  devtools: { enabled: true },
  modules: ["@nuxt/ui", "@nuxtjs/sanity"],
  // 👇 Add these lines
  sanity: {
    projectId: "YOUR-PROJECT-ID",
    dataset: "production",
    apiVersion: "2025-10-01",
    typegen: {
      enabled: true,
      schemaTypesPath: "../studio-hello-world/schemaTypes",
      queryPaths: ["./app/**/*.{ts,tsx,vue}"],
    },
  },
});

4Start the development server

Run the following command and open http://localhost:3000 in your browser.

npm run dev
pnpm run dev
yarn run dev
bun run dev

5Display content on the home page

Nuxt performs data fetching inside script tags at the top of .vue files

Create a route for a page with a list of posts fetched from your Sanity dataset, and visit http://localhost:3000

<script setup lang="ts">
  const postsQuery = groq`*[
    _type == "post"
    && defined(slug.current)
  ]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt}`

  const { data: posts } = await useSanityQuery<PostsQueryResult>(postsQuery)
</script>

<template>
  <main class="container mx-auto min-h-screen max-w-3xl p-8">
    <h1 class="text-4xl font-bold mb-8">Posts</h1>
    <ul class="flex flex-col gap-y-4">
      <li v-for="post in posts" :key="post._id" class="hover:underline">
        <nuxt-link :to="`/${post.slug.current}`">
          <h2 class="text-xl font-semibold">{{ post.title }}</h2>
          <p>{{ new Date(post.publishedAt).toLocaleDateString() }}</p>
        </nuxt-link>
      </li>
    </ul>
  </main>
</template>

6Display individual posts

Create a new route for individual post pages.

The dynamic value of a slug when visiting /[slug] in the URL is used as a parameter in the GROQ query used by Sanity Client.

Notice that we’re using Tailwind CSS Typography’s prose class name to style the post’s body block content.

<script setup lang="ts">
  import type { SanityDocument } from "@sanity/client";
  import {
    createImageUrlBuilder,
    type SanityImageSource,
  } from "@sanity/image-url";

  const POST_QUERY = groq`*[_type == "post" && slug.current == $slug][0]`;
  const { params } = useRoute();

  const { data: post } = await useSanityQuery<SanityDocument>(POST_QUERY, params);
  const { projectId, dataset } = useSanity().client.config();
  const urlFor = (source: SanityImageSource) =>
    projectId && dataset
      ? createImageUrlBuilder({ projectId, dataset }).image(source)
      : null;
</script>

<template>
  <main
    v-if="post"
    class="container mx-auto min-h-screen max-w-3xl p-8 flex flex-col gap-4"
  >
    <a href="/" class="hover:underline">&larr; Back to posts</a>
    <img
      v-if="post.image"
      :src="urlFor(post.image)?.width(550).height(310).url()"
      :alt="post?.title"
      class="aspect-video rounded-xl"
      width="550"
      height="310"
    />
    <h1 v-if="post.title" class="text-4xl font-bold mb-8">{{ post.title }}</h1>
    <div class="prose">
      <p v-if="post.publishedAt">
        Published: {{ new Date(post.publishedAt).toLocaleDateString() }}
      </p>
      <SanityContent v-if="post.body" :blocks="post.body" />
    </div>
  </main>
</template>
# outside your studio directory
npx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
pnpm dlx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
yarn dlx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
bunx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
npx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
pnpm dlx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
yarn dlx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
# outside your studio directory
bunx nuxi@latest init -t ui nuxt-hello-world
cd nuxt-hello-world
├─ /nuxt-hello-world
└─ /studio-hello-world
npx nuxi@latest module add sanity
npm install @sanity/image-url
pnpm dlx nuxi@latest module add sanity
pnpm add @sanity/image-url
yarn dlx nuxi@latest module add sanity
yarn add @sanity/image-url
bunx nuxi@latest module add sanity
bun add @sanity/image-url
npx nuxi@latest module add sanity
npm install @sanity/image-url
pnpm dlx nuxi@latest module add sanity
pnpm add @sanity/image-url
yarn dlx nuxi@latest module add sanity
yarn add @sanity/image-url
bunx nuxi@latest module add sanity
bun add @sanity/image-url
export default defineNuxtConfig({
  compatibilityDate: "2024-04-03",
  devtools: { enabled: true },
  modules: ["@nuxt/ui", "@nuxtjs/sanity"],
  // 👇 Add these lines
  sanity: {
    projectId: "YOUR-PROJECT-ID",
    dataset: "production",
    apiVersion: "2025-10-01",
    typegen: {
      enabled: true,
      schemaTypesPath: "../studio-hello-world/schemaTypes",
      queryPaths: ["./app/**/*.{ts,tsx,vue}"],
    },
  },
});
npm run dev
pnpm run dev
yarn run dev
bun run dev
<script setup lang="ts">
  const postsQuery = groq`*[
    _type == "post"
    && defined(slug.current)
  ]|order(publishedAt desc)[0...12]{_id, title, slug, publishedAt}`

  const { data: posts } = await useSanityQuery<PostsQueryResult>(postsQuery)
</script>

<template>
  <main class="container mx-auto min-h-screen max-w-3xl p-8">
    <h1 class="text-4xl font-bold mb-8">Posts</h1>
    <ul class="flex flex-col gap-y-4">
      <li v-for="post in posts" :key="post._id" class="hover:underline">
        <nuxt-link :to="`/${post.slug.current}`">
          <h2 class="text-xl font-semibold">{{ post.title }}</h2>
          <p>{{ new Date(post.publishedAt).toLocaleDateString() }}</p>
        </nuxt-link>
      </li>
    </ul>
  </main>
</template>
<script setup lang="ts">
  import type { SanityDocument } from "@sanity/client";
  import {
    createImageUrlBuilder,
    type SanityImageSource,
  } from "@sanity/image-url";

  const POST_QUERY = groq`*[_type == "post" && slug.current == $slug][0]`;
  const { params } = useRoute();

  const { data: post } = await useSanityQuery<SanityDocument>(POST_QUERY, params);
  const { projectId, dataset } = useSanity().client.config();
  const urlFor = (source: SanityImageSource) =>
    projectId && dataset
      ? createImageUrlBuilder({ projectId, dataset }).image(source)
      : null;
</script>

<template>
  <main
    v-if="post"
    class="container mx-auto min-h-screen max-w-3xl p-8 flex flex-col gap-4"
  >
    <a href="/" class="hover:underline">&larr; Back to posts</a>
    <img
      v-if="post.image"
      :src="urlFor(post.image)?.width(550).height(310).url()"
      :alt="post?.title"
      class="aspect-video rounded-xl"
      width="550"
      height="310"
    />
    <h1 v-if="post.title" class="text-4xl font-bold mb-8">{{ post.title }}</h1>
    <div class="prose">
      <p v-if="post.publishedAt">
        Published: {{ new Date(post.publishedAt).toLocaleDateString() }}
      </p>
      <SanityContent v-if="post.body" :blocks="post.body" />
    </div>
  </main>
</template>