Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Confluence with V0

To integrate Confluence with V0 by Vercel, generate a knowledge base or documentation UI with V0, create a Next.js API route that calls the Confluence REST API using your API token, store credentials in Vercel environment variables, and deploy. Your app can fetch pages, spaces, and blog posts from Confluence without exposing your API key to the browser.

What you'll learn

  • How to generate an Atlassian API token and locate your Confluence cloud domain
  • How to generate a documentation or knowledge base UI with V0
  • How to create a Next.js API route that fetches Confluence spaces and pages
  • How to render Confluence page content (including Confluence storage format) in your Next.js app
  • How to implement Confluence full-text search in a V0-generated interface
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read30 minutesProductivityApril 2026RapidDev Engineering Team
TL;DR

To integrate Confluence with V0 by Vercel, generate a knowledge base or documentation UI with V0, create a Next.js API route that calls the Confluence REST API using your API token, store credentials in Vercel environment variables, and deploy. Your app can fetch pages, spaces, and blog posts from Confluence without exposing your API key to the browser.

Build Custom Knowledge Base Interfaces Powered by Confluence with V0

Confluence is the documentation backbone for thousands of engineering and product teams. While the Confluence web interface is feature-rich, many teams want to surface specific documentation, release notes, or knowledge base content in their own V0-generated apps — a customer portal, a developer hub, or an internal tool — without requiring users to have Confluence accounts or navigate Confluence's full interface.

The Confluence REST API gives you programmatic access to spaces, pages, blog posts, and search results. You can fetch a page's content, list all pages in a space, search across your entire Confluence instance, and retrieve page labels and metadata. The API uses Basic Authentication with your Atlassian email and an API token (not your account password), making it straightforward to call from a Next.js API route.

One important consideration is content rendering: Confluence stores page body content in its own XML-based storage format (Confluence Storage Format), not raw HTML or Markdown. When retrieving page content, you can request it in the 'storage' format (raw XML), 'view' format (rendered HTML), or 'export_view' format (export-ready HTML). For displaying content in your Next.js app, requesting the 'view' format and sanitizing the HTML before rendering with dangerouslySetInnerHTML is the most practical approach. For production apps, use a library like DOMPurify to sanitize the HTML and prevent XSS vulnerabilities from any malicious content that might exist in Confluence pages.

Integration method

Next.js API Route

Confluence integrates with V0-generated Next.js apps through server-side API routes that call the Confluence REST API using Basic Authentication with an Atlassian API token. Your Confluence domain, email, and API token are stored as server-only Vercel environment variables and never reach the browser. The UI components V0 generates fetch spaces, pages, and blog posts through your Next.js API routes, which proxy calls to the Confluence REST API and return structured content to the frontend.

Prerequisites

  • A Confluence Cloud account with at least one space containing pages — Confluence Cloud is at your-domain.atlassian.net
  • An Atlassian API token — generate one at id.atlassian.com/manage-profile/security/api-tokens
  • Your Confluence Cloud domain (the subdomain part of your Atlassian URL, e.g., 'mycompany' from mycompany.atlassian.net)
  • The space key(s) for the Confluence spaces you want to access — visible in the Confluence space settings or in the URL when browsing a space
  • A V0 account at v0.dev and a Vercel account for deployment

Step-by-step guide

1

Generate the Knowledge Base UI with V0

Open V0 at v0.dev and describe the documentation interface you want to create. Confluence integrations typically fall into a few patterns: a full documentation browser with sidebar navigation and space hierarchy, a searchable knowledge base with article listings, or a specific documentation page renderer. Describe which pattern suits your use case and include the visual style you're targeting — documentation sites often use minimal design with high readability and good typography. When prompting V0, specify the API routes your interface will call: /api/confluence/spaces to list available spaces, /api/confluence/pages to list pages in a space, /api/confluence/page/{id} to fetch a specific page's content, and /api/confluence/search to search across pages. V0 will scaffold fetch calls to these routes in the generated component. For the content rendering component, ask V0 to create a div that will receive HTML content and apply Tailwind's prose classes (from the @tailwindcss/typography plugin) for readable formatting. Confluence's view-format HTML uses standard HTML elements (h1-h6, p, ul, ol, table) that map well to Tailwind prose styles. After generating in V0, push to GitHub using V0's Git panel before creating the API routes.

V0 Prompt

Build a knowledge base browser with a top navigation bar containing a search input. Below the nav, show a two-column layout: a left sidebar with a collapsible list of Confluence spaces (loaded from /api/confluence/spaces), and a main content area. When no space is selected, show a welcome screen with space cards. When a space is clicked, load its pages in the sidebar from /api/confluence/pages?spaceKey={key} and show the page list. When a page is clicked, fetch its content from /api/confluence/page/{id} and render the HTML content in the main area. Show a breadcrumb navigation and a last-updated timestamp above the content.

Paste this in V0 chat

Pro tip: Ask V0 to add the @tailwindcss/typography plugin and apply the 'prose' class to the page content container. This automatically styles headings, paragraphs, lists, and code blocks from Confluence page HTML with professional documentation-style typography.

Expected result: A knowledge base UI renders in V0's preview with a sidebar for space/page navigation and a main content area. The component references /api/confluence/spaces, /api/confluence/pages, and /api/confluence/page/{id} for data.

2

Create the Confluence API Routes

Create the Next.js API routes that proxy requests to the Confluence REST API. The Confluence Cloud REST API base URL follows the pattern https://{domain}.atlassian.net/wiki/rest/api. All requests use Basic Authentication with your Atlassian account email and API token encoded as base64(email:token) in the Authorization header. Create app/api/confluence/spaces/route.ts for fetching available spaces, app/api/confluence/pages/route.ts for listing pages within a space (accepts spaceKey query param), app/api/confluence/page/[id]/route.ts for fetching a specific page's content, and app/api/confluence/search/route.ts for full-text search. For the page content endpoint, add expand=body.view to the query parameters so Confluence returns the rendered HTML body instead of just page metadata. The response body is in data.body.view.value as an HTML string. Important: sanitize this HTML before rendering it in your React component. While your own Confluence content is trusted, building the sanitization habit protects against future content issues. Install DOMPurify (or use its server-side variant isomorphic-dompurify) and call DOMPurify.sanitize(htmlContent) before passing to dangerouslySetInnerHTML. For the search endpoint, the Confluence REST API exposes a CQL (Confluence Query Language) search at /wiki/rest/api/search. Pass a cql parameter like text ~ "your search query" AND type = page to search page content. The response includes an array of results with title, excerpt, and page URL.

app/api/confluence/page/[id]/route.ts
1// app/api/confluence/page/[id]/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4function getAuthHeader() {
5 const domain = process.env.CONFLUENCE_DOMAIN;
6 const email = process.env.CONFLUENCE_EMAIL;
7 const token = process.env.CONFLUENCE_API_TOKEN;
8 if (!domain || !email || !token) {
9 throw new Error('Confluence credentials not configured');
10 }
11 const encoded = Buffer.from(`${email}:${token}`).toString('base64');
12 return { auth: `Basic ${encoded}`, domain };
13}
14
15export async function GET(
16 _request: NextRequest,
17 { params }: { params: { id: string } }
18) {
19 const { id } = params;
20
21 if (!id) {
22 return NextResponse.json({ error: 'Page ID is required' }, { status: 400 });
23 }
24
25 try {
26 const { auth, domain } = getAuthHeader();
27 const url = `https://${domain}.atlassian.net/wiki/rest/api/content/${id}?expand=body.view,version,ancestors,space`;
28
29 const response = await fetch(url, {
30 headers: {
31 Authorization: auth,
32 Accept: 'application/json',
33 },
34 next: { revalidate: 300 }, // Cache pages for 5 minutes
35 });
36
37 if (!response.ok) {
38 return NextResponse.json(
39 { error: `Confluence API error: ${response.statusText}` },
40 { status: response.status }
41 );
42 }
43
44 const data = await response.json();
45
46 return NextResponse.json({
47 id: data.id,
48 title: data.title,
49 body: data.body?.view?.value ?? '',
50 version: data.version?.number,
51 lastModified: data.version?.when,
52 author: data.version?.by?.displayName,
53 space: data.space?.name,
54 ancestors: data.ancestors?.map((a: { id: string; title: string }) => ({
55 id: a.id,
56 title: a.title,
57 })),
58 });
59 } catch (error) {
60 const msg = error instanceof Error ? error.message : 'Unknown error';
61 return NextResponse.json({ error: msg }, { status: 500 });
62 }
63}

Pro tip: Always sanitize Confluence page HTML before rendering with dangerouslySetInnerHTML. Install isomorphic-dompurify and call DOMPurify.sanitize(content) on the body HTML string before passing it to React. This prevents XSS from any script tags that might exist in Confluence page content.

Expected result: GET /api/confluence/page/12345 returns structured JSON with the page title, HTML body content, version, last modified date, and ancestor breadcrumbs. The HTML is ready to sanitize and render.

3

Create the Spaces and Search Routes

Create the remaining API routes for listing spaces and searching content. The spaces route is a GET request to https://{domain}.atlassian.net/wiki/rest/api/space that returns all spaces accessible with your credentials. Add a type=global parameter to filter out personal spaces (type=personal) which are individual user spaces rather than team spaces. Each space object includes key (the short identifier used in URLs and API calls), name, and description. The pages route accepts a spaceKey query parameter and calls https://{domain}.atlassian.net/wiki/rest/api/content?type=page&spaceKey={key}. Add expand=version to get last-modified information alongside page metadata. You can filter to only top-level pages (no parent) by adding ancestor=root to the query, or retrieve a nested page hierarchy by calling the space's page tree endpoint. The search route accepts a q query parameter and constructs a CQL query for the Confluence search endpoint at https://{domain}.atlassian.net/wiki/rest/api/search. Build the CQL as: text ~ "{searchTerm}" AND type = page — wrap the search term in quotes to handle phrases. URL-encode the CQL query before appending it to the request URL. The search results include a title, excerpt, url, and lastModified fields that your frontend displays in a search results dropdown or dedicated results page.

V0 Prompt

Update the search bar to debounce input by 400ms and call /api/confluence/search?q={query} when 3+ characters are typed. Display results in a dropdown panel below the search bar with each result showing the page title, a brief excerpt, and which space it belongs to. Clicking a result navigates to that page's content. Show a 'No results found' state for empty results and a loading spinner while searching.

Paste this in V0 chat

app/api/confluence/search/route.ts
1// app/api/confluence/search/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4function getAuthHeader() {
5 const domain = process.env.CONFLUENCE_DOMAIN;
6 const email = process.env.CONFLUENCE_EMAIL;
7 const token = process.env.CONFLUENCE_API_TOKEN;
8 if (!domain || !email || !token) {
9 throw new Error('Confluence credentials not configured');
10 }
11 const encoded = Buffer.from(`${email}:${token}`).toString('base64');
12 return { auth: `Basic ${encoded}`, domain };
13}
14
15export async function GET(request: NextRequest) {
16 const { searchParams } = new URL(request.url);
17 const query = searchParams.get('q');
18
19 if (!query || query.length < 2) {
20 return NextResponse.json({ results: [] });
21 }
22
23 try {
24 const { auth, domain } = getAuthHeader();
25 // Build CQL search query
26 const cql = `text ~ "${query.replace(/"/g, '')}" AND type = page`;
27 const url = `https://${domain}.atlassian.net/wiki/rest/api/search?cql=${encodeURIComponent(cql)}&limit=10&expand=content.space`;
28
29 const response = await fetch(url, {
30 headers: {
31 Authorization: auth,
32 Accept: 'application/json',
33 },
34 });
35
36 if (!response.ok) {
37 return NextResponse.json(
38 { error: `Search failed: ${response.statusText}` },
39 { status: response.status }
40 );
41 }
42
43 const data = await response.json();
44
45 const results = data.results.map((r: {
46 content: { id: string; title: string; space: { name: string } };
47 excerpt: string;
48 url: string;
49 }) => ({
50 id: r.content.id,
51 title: r.content.title,
52 excerpt: r.excerpt,
53 space: r.content.space?.name,
54 url: r.url,
55 }));
56
57 return NextResponse.json({ results });
58 } catch (error) {
59 const msg = error instanceof Error ? error.message : 'Unknown error';
60 return NextResponse.json({ error: msg }, { status: 500 });
61 }
62}

Pro tip: Confluence search results include HTML markup in the excerpt field (surrounding matching text with <em> tags). Strip these tags before displaying in plain text contexts, or render the excerpt as HTML in your search results to show the highlighted match terms.

Expected result: GET /api/confluence/search?q=deployment returns an array of matching pages with titles, excerpts, and space names. GET /api/confluence/spaces returns all team spaces with their keys and names.

4

Add Environment Variables and Deploy to Vercel

Push your code to GitHub and configure Confluence credentials in Vercel. Open the Vercel Dashboard, select your project, and navigate to Settings → Environment Variables. Add three variables: CONFLUENCE_DOMAIN (just the subdomain part, e.g., 'mycompany' not 'mycompany.atlassian.net'), CONFLUENCE_EMAIL (your Atlassian account email address), and CONFLUENCE_API_TOKEN (the API token generated at id.atlassian.com/manage-profile/security/api-tokens). None of these variables should have the NEXT_PUBLIC_ prefix — all Confluence API calls must happen server-side since they use credentials that must not reach the browser. Add them for Production, Preview, and Development environments. For local development, also add them to .env.local in your project root. After saving the variables, click Redeploy in the Deployments tab. Once the deployment completes, open your app's URL and verify that spaces load in the navigation, page content renders correctly, and the search returns relevant results. If pages render with broken layout, it may be because Confluence's view format HTML references Atlassian CDN resources (images, attachments) that require authentication — those resources will not load in your embedded view. Work around this by fetching page attachments separately via the API and serving them through your own routes, or accept that some Confluence-hosted images won't display.

Pro tip: Confluence API tokens expire and must be regenerated at id.atlassian.com. If your app suddenly stops fetching Confluence data, check whether the token has expired and generate a new one, then update the CONFLUENCE_API_TOKEN environment variable in Vercel and redeploy.

Expected result: The deployed Vercel app displays Confluence spaces in the sidebar, renders page content from your Confluence instance, and returns search results matching page content. The Confluence API token is never visible in the browser network requests.

Common use cases

Internal Documentation Hub

Build a searchable internal knowledge base that pulls content directly from Confluence. Employees can search pages, browse spaces, and read documentation without logging into Confluence. Display pages with your company's branding and navigation structure rather than Confluence's default interface.

V0 Prompt

Create a documentation hub with a left sidebar showing Confluence spaces as navigation items (fetched from /api/confluence/spaces). When a space is clicked, show the list of pages in that space in the main content area. When a page is clicked, display its full content with a breadcrumb trail. Include a search bar at the top that calls /api/confluence/search?q={query} and shows results in a dropdown. Use a clean documentation site style with a white background and subtle gray sidebar.

Copy this prompt to try it in V0

Customer-Facing Knowledge Base

Expose a curated subset of your Confluence documentation to customers through a branded portal. Fetch pages from a specific Confluence space designated as public-facing, and display them with your product's design system. Users can search and browse without needing a Confluence account.

V0 Prompt

Build a customer knowledge base portal with a hero section showing a search bar and category cards. Below, show article listings from /api/confluence/pages?spaceKey=HELP organized by label (Getting Started, Billing, Integrations). Each article card shows title, a brief excerpt, and last-updated date. Clicking an article opens it in a full-width reading view. Include a 'Was this helpful?' thumbs up/down at the bottom of each article.

Copy this prompt to try it in V0

Release Notes Dashboard

Automatically surface your team's Confluence blog posts or release notes pages in a changelog-style dashboard. Fetch the latest blog posts from a specific space and display them chronologically with filtering by version or product area.

V0 Prompt

Design a product changelog page that fetches recent blog posts from /api/confluence/blogposts?spaceKey=PRODUCT and displays them as a vertical timeline. Each entry shows the title, publication date, author avatar (from the author's Confluence profile), and a brief preview of the content. Include version badge labels (fetched from page labels). Add a filter bar to show entries by label type. Link each entry to the full page view.

Copy this prompt to try it in V0

Troubleshooting

API route returns 401 or 403 when calling the Confluence API

Cause: The API token is invalid or expired, the email address doesn't match the Atlassian account that owns the token, or the account lacks permission to access the requested space.

Solution: Regenerate the API token at id.atlassian.com/manage-profile/security/api-tokens and update CONFLUENCE_API_TOKEN in Vercel. Ensure CONFLUENCE_EMAIL matches the account email exactly. Confirm the account has at least view permission on the target Confluence spaces by checking Confluence space permissions under Space Settings → Permissions.

Page content renders as raw XML or escaped HTML instead of formatted text

Cause: The API request is returning the storage format (XML) instead of the view format (rendered HTML), because the expand=body.view query parameter is missing or incorrectly spelled.

Solution: Verify your content endpoint URL includes the expand=body.view parameter. The view format key in the response is data.body.view.value — if you access data.body.storage.value instead, you'll get the raw XML. Double-check the expand parameter spelling exactly as shown.

typescript
1// Correct URL with view format
2const url = `https://${domain}.atlassian.net/wiki/rest/api/content/${id}?expand=body.view`;
3// Access the value correctly
4const htmlContent = data.body?.view?.value ?? '';

Images inside Confluence pages don't load in the rendered content

Cause: Confluence page images are typically hosted on the Atlassian CDN and require authentication to access. When your app renders the page HTML, the browser cannot load these image URLs because they require the same Atlassian credentials.

Solution: For a quick fix, add a note to users that some images may not display. For a proper fix, parse the page HTML to find all image URLs, fetch each image server-side with your Confluence credentials using the attachment API, and replace the original URLs with either base64 data URIs or proxied routes through your Next.js app.

Search returns no results even though the query should match content

Cause: The CQL query syntax is malformed, or the search term contains special characters that break the CQL string.

Solution: Strip quotes and special CQL characters from user input before building the CQL string. Test your CQL directly in Confluence's advanced search (accessible via the search bar → Advanced search) to verify the query syntax works before debugging the API route.

typescript
1// Sanitize search input for CQL
2const safeTerm = query.replace(/[\"|\\|\*|\?|&|\||\/]/g, ' ').trim();
3const cql = `text ~ "${safeTerm}" AND type = page`;

Best practices

  • Cache Confluence API responses using Next.js fetch caching — spaces and pages change infrequently and caching for 5-15 minutes dramatically reduces API calls and improves page load times
  • Sanitize page HTML content with DOMPurify or isomorphic-dompurify before rendering with dangerouslySetInnerHTML to prevent XSS from any script content that might exist in Confluence pages
  • Store only the minimum Confluence credentials needed — use a dedicated Atlassian service account with read-only space access rather than admin credentials
  • Handle pagination in the spaces and pages routes using Confluence's limit and start parameters — large Confluence instances may have hundreds of spaces or thousands of pages
  • Show last-modified dates prominently on pages to help users identify stale documentation — Confluence API always returns the last version timestamp
  • Build a fallback for when Confluence is unavailable — show a cached version or a friendly 'documentation temporarily unavailable' message rather than crashing your app
  • Filter spaces by type=global to exclude personal spaces from navigation — personal spaces (prefixed with ~ in the key) are user-specific workspaces that should not appear in a team documentation hub

Alternatives

Frequently asked questions

Do I need admin access to Confluence to set up this integration?

No — you only need a regular Confluence user account with read access to the spaces you want to display. You need permission to create an Atlassian API token (available to all users at id.atlassian.com) and view permission on the target spaces. Admin access is only required if you want to write back to Confluence (create or edit pages) from your app.

Can I edit or create Confluence pages from my V0 app?

Yes — the Confluence REST API supports creating pages (POST /wiki/rest/api/content), updating pages (PUT /wiki/rest/api/content/{id}), and adding comments. For creating pages, you need to send content in Confluence Storage Format (XML), which is complex to generate. An easier approach is to use the wiki format option or Atlassian Document Format (ADF) which Confluence also accepts. Only implement write operations if specifically needed, since read-only access covers most documentation display use cases.

Does this work with Confluence Server/Data Center or only Confluence Cloud?

The REST API patterns in this guide are for Confluence Cloud (your-domain.atlassian.net). Confluence Server and Data Center use a similar but slightly different REST API base URL and authentication approach. Server/Data Center uses personal access tokens (PATs) or session-based auth rather than API tokens. The overall pattern — Next.js API route proxying to Confluence — is the same, but the auth header construction differs.

How do I restrict which Confluence spaces are exposed in my app?

Filter the spaces response in your API route by maintaining an allowlist of space keys that should be visible. Check each space's key against the allowlist before including it in the response. Alternatively, create a dedicated Confluence space for public-facing content and configure your API route to only fetch from that space key, ignoring all others.

Can I add real-time updates when Confluence pages change?

Confluence Cloud supports webhooks that can notify your Next.js app when pages are created, updated, or deleted. Configure a webhook in Confluence under Space Settings → Integrations → Webhooks, pointing to a route like /api/confluence/webhook in your app. Your webhook handler can then invalidate cached pages and trigger a revalidation. This keeps your displayed content fresh without polling.

How do I handle Confluence page hierarchies (parent/child pages) in my navigation?

Use the ancestors field (by adding expand=ancestors to your page request) to get the parent page chain for breadcrumb navigation. To build a full tree, fetch the children of a page using /wiki/rest/api/content/{id}/child/page. For deep hierarchies, consider fetching lazily — only load children when a parent node is expanded in your navigation, rather than loading the entire tree upfront.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.