---
title: "Server Components"
description: "Render individual components on the server only, keeping their JavaScript out of your client bundle."
canonical_url: "https://nuxt.com/docs/4.x/guide/concepts/server-components"
---
# Server Components

> Render individual components on the server only, keeping their JavaScript out of your client bundle.

Nuxt renders your app on the server by default, but then it ships the JavaScript for every component to the browser and hydrates the whole page. For content-heavy components (markdown rendering, syntax highlighting, CMS output) that never change on the client, this is wasted work: the user downloads, parses and executes code whose only job is to reproduce HTML that is already on the page.

Server components (also called island components) invert this. A server component is rendered on the server, its HTML is embedded in the page, and none of its JavaScript is sent to the client. Its dependencies (a markdown parser, a highlighting library) stay on the server too.

<tip icon="i-lucide-newspaper" target="_blank" to="https://roe.dev/blog/nuxt-server-components">

Read Daniel Roe's guide to Nuxt Server Components.

</tip>

<video-accordion title="Watch Learn Vue video about Nuxt Server Components" video-id="u1yyXe86xJM">



</video-accordion>

## Enabling Server Components

Component islands are controlled by [`experimental.componentIslands`](https://nuxt.com/docs/4.x/guide/going-further/experimental-features#componentislands). The default value is `'auto'`, which enables the feature automatically as soon as your app contains a server component or island, so in most cases you do not need any configuration.

Set the option explicitly if you want remote islands or selective client hydration:

```ts [nuxt.config.ts]twoslash
export default defineNuxtConfig({
  experimental: {
    componentIslands: {
      selectiveClient: true, // or 'deep', to enable `nuxt-client`
      remoteIsland: false, // allow rendering islands from a remote source
    },
  },
})
```

<read-more to="https://github.com/nuxt/nuxt/issues/19772" icon="i-simple-icons-github" target="_blank">

Server components are still marked experimental. You can follow the roadmap on GitHub.

</read-more>

## `.server.vue` Components

Add the `.server` suffix to a component to make it a standalone server component:

```bash [Directory Structure]
-| app/
---| components/
-----| HighlightedMarkdown.server.vue
```

Use it like any other component:

```vue [app/pages/example.vue]
<template>
  <div>
    <!--
      rendered on the server; the markdown parsing and highlighting
      libraries are not included in your client bundle
     -->
    <HighlightedMarkdown markdown="# Headline" />
  </div>
</template>
```

Components inside `~/components/islands/` are also registered as islands and can be rendered with [`<NuxtIsland>`](https://nuxt.com/docs/4.x/api/components/nuxt-island) directly, for example `<NuxtIsland name="MyIsland" />` for `~/components/islands/MyIsland.vue`.

You can also pair a `.server.vue` component with a `.client.vue` component of the same name for [separate server and client implementations](https://nuxt.com/docs/4.x/directory-structure/app/components#paired-with-a-client-component). In that case the component is not an island: the client half hydrates normally.

<warning>

Server components (and islands) must have a single root element. (HTML comments are considered elements as well.)

</warning>

## How Islands Are Rendered

Server components use [`<NuxtIsland>`](https://nuxt.com/docs/4.x/api/components/nuxt-island) under the hood. Rendering an island issues a request to a dedicated island endpoint, which:

- creates a **new, isolated Vue app** on the server to render just that component
- creates an 'island context' that you can access via `nuxtApp.ssrContext.islandContext` inside the island
- runs your plugins again, unless they set `env: { islands: false }` (object-syntax plugins)

Because the island is isolated from the rest of your app:

- you cannot share state (provide/inject, Pinia, `useState`) between the page and the island; pass data via props instead
- [`useRoute()`](https://nuxt.com/docs/4.x/api/composables/use-route) inside an island reflects the island's own request, not the page the user is on. If an island needs route information, pass it in explicitly, either as props or via the `context` prop on `<NuxtIsland>` (read inside the island from `nuxtApp.ssrContext.islandContext`)
- route middleware does not run when rendering islands

Props are serialized and sent as **GET query parameters**. This makes island responses cacheable, but it also means:

- props must be JSON-serializable
- props are limited by URL length, so avoid passing large amounts of data
- props may be visible in server access logs, CDN caches and `Referer` headers

<note>

Because props come from the request (URL query or body), treat them as untrusted input. Nuxt rejects the props most likely to leak through unintentionally: a top-level `as` that the island does not declare (an undeclared prop falls through as an attribute onto the island's root), and, with `vue.runtimeCompiler` enabled, a `template` anywhere in the props. Beyond that, avoid feeding props you have not validated into dynamic component resolution (`<component :is>`, `h()`, `resolveDynamicComponent()`, or a polymorphic `as` / `asChild` prop), since a string can resolve to any registered component or HTML element.

Props a component does not declare fall through as attributes onto its single root element, so an island whose root is a polymorphic component (e.g. from `reka-ui` / `@nuxt/ui`) can receive attributes you did not bind. Set `defineOptions({ inheritAttrs: false })` on such islands, or declare the props you accept.

To switch components based on caller input, map a discriminator through an allowlist of imported components rather than passing the raw prop:

```vue
<script setup lang="ts">
import type { Component } from 'vue'
import CardA from './CardA.vue'
import CardB from './CardB.vue'

const props = defineProps<{ variant: string }>()
const allowed: Record<string, Component> = { a: CardA, b: CardB }
const component = allowed[props.variant] ?? CardA
</script>

<template>
  <component :is="component" />
</template>
```

</note>

Changing an island's props triggers a network request that re-renders the component on the server and updates its HTML in place.

<read-more to="https://nuxt.com/docs/4.x/api/components/nuxt-island">

Read the full `<NuxtIsland>` API documentation, including props, slots, events and known limitations.

</read-more>

## Selective Hydration with `nuxt-client`

An island is static by default, but you can hydrate individual components inside it by adding the `nuxt-client` attribute. This requires `experimental.componentIslands.selectiveClient` to be enabled.

```vue [app/components/ServerWithClient.server.vue]
<template>
  <div>
    <HighlightedMarkdown markdown="# Headline" />
    <!-- Counter will be loaded and hydrated client-side -->
    <Counter
      nuxt-client
      :count="5"
    />
  </div>
</template>
```

The component marked with `nuxt-client` is server-rendered as part of the island, then hydrated by the main client app. Only its chunk is shipped to the client; the rest of the island remains static.

Setting `selectiveClient: 'deep'` additionally allows passing slots to `nuxt-client` components. Those slots are rendered on the server and are **not interactive** on the client.

<warning>

Use `nuxt-client` only on local `.vue` SFCs. Built-ins like [`<NuxtLink>`](https://nuxt.com/docs/4.x/api/components/nuxt-link) skip the islands transform. After client navigation you may see `Failed to locate Teleport target`, or the link disappears with no error. Wrap the built-in in your own `.vue` file and put `nuxt-client` on that wrapper. See [#29251](https://github.com/nuxt/nuxt/issues/29251) and [#26002](https://github.com/nuxt/nuxt/issues/26002).

</warning>

## Slots

Slots can be passed to an island component if declared in the island. Slot content is provided by the parent, so it belongs to the main client app and **is** interactive (it is wrapped in a `<div>` with `display: contents;`).

`<NuxtIsland>` reserves the `#fallback` slot to specify content rendered before the island loads (when `lazy` is set) or when fetching the island fails.

## The Client Navigation Round Trip

On the initial server-rendered page load, islands are rendered inline and there is no extra request. On **client-side navigation**, however, each island on the destination page must be fetched from the server (you can see these requests in the network tab). This has real costs:

- islands block on a network round trip during navigation, unless you pass the `lazy` prop (with a `#fallback` slot) to render them non-blockingly
- an app with many islands per page makes many requests per navigation

Islands work best on pages that are reached by full page loads (content and marketing pages) or when their number per page is small. If a component needs to update frequently on the client, an island is probably the wrong tool.

## Prerendering and Caching

Islands play well with static and cached rendering:

- during prerendering (`nuxt generate` or `prerender` route rules), island responses are cached, so identical islands (same name, props and context) are rendered once and reused
- because props travel as GET query parameters, island responses can also be cached by your server or CDN at the island endpoint level
- two instances of the same island with the same props share a single server render and payload entry

Note that island responses being keyed only on name, props and context is exactly what keeps them cacheable independently of the page they appear on; this is also why they cannot see the current route (see above).

If you are building a mostly-static site, islands combine well with `prerender` and `noScripts` route rules.

<read-more to="https://nuxt.com/docs/4.x/guide/recipes/mostly-static-sites">

See the mostly-static site recipe for combining prerendering, `noScripts`, islands and lazy hydration.

</read-more>

One interaction to be aware of: island slots and `nuxt-client` components rely on a small inline script to relocate teleported content into place before hydration. On routes rendered with [`noScripts`](https://nuxt.com/docs/4.x/guide/going-further/features#noscripts), that script is omitted, so fully interactive `nuxt-client` components will not hydrate there. Plain static islands are unaffected.

## Current Limitations

Server components are experimental, and some rough edges are tracked in open issues:

- Most features for server-only and island components, such as slots and `nuxt-client` components, are only available for single file components.
- Using islands can significantly increase the number of chunks generated at build time ([#34855](https://github.com/nuxt/nuxt/issues/34855)).
- With webpack and Rspack, scoped `:slotted()` styles in server component slots can fail because server and client builds may generate different scope IDs ([#31510](https://github.com/nuxt/nuxt/issues/31510)).
- Template refs cannot reference elements inside a server component from the parent ([#31512](https://github.com/nuxt/nuxt/issues/31512)).
- `inject`/`provide` does not cross the island boundary, so injecting from the page into a standalone server component does not work ([#22751](https://github.com/nuxt/nuxt/issues/22751)).
- Server components rendered via the auto-generated wrapper do not expose load and error events; use `<NuxtIsland>` directly if you need its `error` event and `refresh()` method ([#25744](https://github.com/nuxt/nuxt/issues/25744)).
- [`useId`](https://vuejs.org/api/composition-api-helpers#useid) has known limitations inside islands; see the [`<NuxtIsland>` documentation](https://nuxt.com/docs/4.x/api/components/nuxt-island#known-limitations).
- Each nested island adds extra overhead, so be careful when nesting islands within other islands.

<read-more to="https://nuxt.com/docs/4.x/directory-structure/app/components#server-components">

Read more about server component file conventions in the components directory documentation.

</read-more>


## Sitemap

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