---
title: "Quick Start Tutorial"
description: "Build a blog with WPNuxt in 15 minutes"
canonical_url: "https://wpnuxt.com/getting-started/quick-start"
---
# Quick Start Tutorial

> Build a blog with WPNuxt in 15 minutes

Build a working blog with WPNuxt from scratch. This tutorial covers fetching posts, displaying content, and creating dynamic pages.

<callout type="tip">

**Want to skip the manual setup?** Run `npm create wpnuxt@latest` to get a fully configured project with Nuxt UI, dynamic pages, menus, and a WordPress Playground for local development. See [Installation](https://wpnuxt.com/getting-started/installation).

</callout>

## Prerequisites

- Node.js 20+
- A WordPress site with [WPGraphQL](https://wordpress.org/plugins/wp-graphql/) installed
- Basic familiarity with Vue and Nuxt

<note>

Don't have a WordPress site? Use our demo: `https://wordpress.wpnuxt.com`

</note>

## Step 1: Create a Nuxt Project

```bash
npx nuxi@latest init my-wpnuxt-blog
cd my-wpnuxt-blog
pnpm install
```

## Step 2: Install WPNuxt

```bash
pnpm add @wpnuxt/core
```

## Step 3: Configure

Create or update `nuxt.config.ts`:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['@wpnuxt/core'],

  wpNuxt: {
    wordpressUrl: 'https://wordpress.wpnuxt.com'
  }
})
```

Or use an environment variable:

```bash [.env]
WPNUXT_WORDPRESS_URL=https://wordpress.wpnuxt.com
```

## Step 4: Generate Types

Run prepare to download the GraphQL schema and generate types:

```bash
pnpm nuxt prepare
```

You should see:

```text
[wpnuxt] WPNuxt module loaded in XXXms
```

## Step 5: Create the Home Page

Replace `app.vue` with a simple layout, then create the home page.

```vue [app.vue]
<template>
  <div class="container">
    <NuxtPage />
  </div>
</template>

<style>
.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}
</style>
```

Create the pages directory and home page:

```vue [pages/index.vue]
<script setup lang="ts">
// Fetch the 10 most recent posts
const { data: posts } = await usePosts({ first: 10 })
</script>

<template>
  <div>
    <h1>My Blog</h1>

    <div v-if="posts?.length">
      <article v-for="post in posts" :key="post.id">
        <h2>
          <NuxtLink :to="post.uri">
            {{ post.title }}
          </NuxtLink>
        </h2>
        <p>{{ post.date }}</p>
        <div v-sanitize-html="post.excerpt" />
      </article>
    </div>

    <p v-else>No posts found.</p>
  </div>
</template>

<style scoped>
article {
  margin-bottom: 2rem;
  padding-bottom: 2rem;
  border-bottom: 1px solid #eee;
}

h2 {
  margin-bottom: 0.5rem;
}

h2 a {
  color: inherit;
  text-decoration: none;
}

h2 a:hover {
  text-decoration: underline;
}
</style>
```

## Step 6: Start the Dev Server

```bash
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000). You should see a list of blog posts!

<callout type="info">

`usePosts()` blocks navigation until data is ready — this means the page won't render until posts are loaded, which is ideal for SEO (search engines see the full content). For non-critical content like sidebars, use `{ lazy: true }` to load data in the background without blocking. See [Fetching Data](https://wpnuxt.com/guide/fetching-data) for details.

</callout>

## Step 7: Create the Single Post Page

Create a dynamic route for individual posts:

```vue [pages/[...slug].vue]
<script setup lang="ts">
const route = useRoute()

// Fetch post or page by URI
const { data: node } = await useNodeByUri({ uri: route.path })

// Handle 404
if (!node.value) {
  throw createError({
    statusCode: 404,
    message: 'Page not found'
  })
}

// Set page title
useHead({
  title: node.value.title
})
</script>

<template>
  <article v-if="node">
    <h1>{{ node.title }}</h1>

    <p v-if="'date' in node" class="date">
      Published: {{ new Date(node.date).toLocaleDateString() }}
    </p>

    <div v-sanitize-html="node.content" class="content" />

    <NuxtLink to="/">← Back to home</NuxtLink>
  </article>
</template>

<style scoped>
.date {
  color: #666;
  margin-bottom: 2rem;
}

.content {
  line-height: 1.7;
}

.content :deep(img) {
  max-width: 100%;
  height: auto;
}
</style>
```

Click on a post title on the home page — you'll now see the full post content!

<callout type="info">

The `[...slug].vue` file is a Nuxt [catch-all route](https://nuxt.com/docs/guide/directory-structure/pages#catch-all-route). It matches any URL path (e.g. `/hello-world`, `/about`, `/blog/my-post`). The `useNodeByUri()` composable sends that path to WordPress and asks "what content lives at this URI?" — WordPress returns a post, page, or any content type registered at that URL.

</callout>

## Step 8: Add a Navigation Menu

Fetch a WordPress menu and display it:

```vue [components/SiteNav.vue]
<script setup lang="ts">
// Fetch menu by name (created in WordPress Appearance → Menus)
const { data: menuItems } = await useMenu({ name: 'Primary Menu' })
</script>

<template>
  <nav v-if="menuItems?.length">
    <NuxtLink
      v-for="item in menuItems"
      :key="item.id"
      :to="item.uri"
    >
      {{ item.label }}
    </NuxtLink>
  </nav>
</template>

<style scoped>
nav {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid #eee;
}

a {
  color: inherit;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}
</style>
```

Add to your layout:

```vue [app.vue]
<template>
  <div class="container">
    <SiteNav />
    <NuxtPage />
  </div>
</template>
```

<note>

Create a menu named "Primary Menu" in WordPress under **Appearance → Menus**. The `name` parameter matches the menu name (or slug).

</note>

## Step 9: Add Featured Images (Optional)

Show featured images on the post list:

```vue [pages/index.vue]
<script setup lang="ts">
const { data: posts } = await usePosts({ first: 10 })
</script>

<template>
  <div>
    <h1>My Blog</h1>

    <div v-if="posts?.length">
      <article v-for="post in posts" :key="post.id">
        <img
          v-if="post.featuredImage?.node?.sourceUrl"
          :src="post.featuredImage.node.sourceUrl"
          :alt="post.featuredImage.node.altText"
          class="featured-image"
        />
        <h2>
          <NuxtLink :to="post.uri">
            {{ post.title }}
          </NuxtLink>
        </h2>
        <div v-sanitize-html="post.excerpt" />
      </article>
    </div>
  </div>
</template>

<style scoped>
.featured-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: 1rem;
}
</style>
```

## Step 10: Add SEO Meta Tags

Your blog already sets the page title in Step 7. Let's add proper meta tags for search engines and social sharing using Nuxt's `useSeoMeta()`:

```vue [pages/[...slug].vue]
<script setup lang="ts">
const route = useRoute()

const { data: node } = await useNodeByUri({ uri: route.path })

if (!node.value) {
  throw createError({
    statusCode: 404,
    message: 'Page not found'
  })
}

useSeoMeta({
  title: node.value.title,
  ogTitle: node.value.title,
  description: 'excerpt' in node.value ? node.value.excerpt : '',
  ogDescription: 'excerpt' in node.value ? node.value.excerpt : '',
  ogImage: 'featuredImage' in node.value
    ? node.value.featuredImage?.node?.sourceUrl
    : undefined,
})
</script>
```

This sets the `<title>`, `<meta name="description">`, and Open Graph tags so search engines and social platforms display your content correctly.

<callout type="tip">

For JSON-LD structured data, sitemaps, and canonical URLs, see the full [SEO guide](https://wpnuxt.com/guide/seo).

</callout>

## What You've Learned

In this tutorial you've used the key building blocks of a WPNuxt project:

- **usePosts()** — Fetch a list of posts with auto-generated composables
- **useNodeByUri()** — Resolve any WordPress URL to its content
- **useMenu()** — Fetch editor-managed navigation menus
- **[...slug].vue** — Catch-all route pattern for dynamic WordPress content
- **v-sanitize-html** — Safely render WordPress HTML content
- **useSeoMeta()** — Set meta tags for SEO and social sharing

## What's Next?

You now have a working blog! Here's where to go from here:

- [How WPNuxt Works](https://wpnuxt.com/getting-started/how-it-works) — Understand the architecture and composable generation pipeline
- [Custom Queries](https://wpnuxt.com/guide/custom-queries) — Fetch exactly the data you need
- [Rendering Blocks](https://wpnuxt.com/guide/rendering-blocks) — Use Gutenberg blocks as Vue components
- [SEO](https://wpnuxt.com/guide/seo) — JSON-LD, sitemaps, and canonical URLs
- [Caching](https://wpnuxt.com/guide/caching) — Optimize performance
- [Deployment](https://wpnuxt.com/deploy/vercel) — Deploy to production

## Complete Code

Here's the final project structure:

```text
my-wpnuxt-blog/
├── app.vue
├── nuxt.config.ts
├── pages/
│   ├── index.vue
│   └── [...slug].vue
├── components/
│   └── SiteNav.vue
└── .env
```

## Troubleshooting

### "Cannot find module" errors

Run `pnpm nuxt prepare` to regenerate types.

### No posts showing

1. Check that WordPress has published posts
2. Verify WPGraphQL is installed and activated
3. Test the GraphQL endpoint: `https://your-wordpress.com/graphql`

### Menu not loading

1. Create a menu in WordPress under **Appearance → Menus**
2. Assign it to the "Primary" location
3. Check that menu items are published pages/posts


## Sitemap

See the full [sitemap](https://wpnuxt.com/sitemap.md) for all pages.
