---
title: "Dynamic Rendering in React, Vue and Angular for SEO"
url: https://encited.com/blog/dynamic-rendering-react-vue-angular
description: "Dynamic rendering in React, Vue and Angular: component maps, conditional rendering and lists with examples, and how to keep those pages visible to crawlers."
lang: en
---

Image: Dynamic Rendering in React, Vue and Angular for SEO: Example Implementations and Patterns (https://encited.com/blog/thumbnails/dynamic-rendering-react-vue-angular.webp)

# Dynamic Rendering in React, Vue and Angular for SEO: Example Implementations and Patterns

Oct 8, 2026 October 8, 2026 · Updated Oct 9, 2026 October 9, 2026 · by Aki from Encited

Dynamic rendering in React, Vue and Angular: component maps, conditional rendering and lists with examples, and how to keep those pages visible to crawlers.

"Dynamic rendering" has two meanings in React, Vue and Angular: choosing which components render at runtime, and serving crawlers a rendered snapshot of the page.

A React, Vue or Angular app built as a single-page app sends crawlers one HTML file with an empty container and a list of scripts. Googlebot runs those scripts later. AI crawlers and link previews never do. Dynamic rendering fixes this without touching your framework code: crawlers get a rendered HTML snapshot of each page, and people keep getting the app. Encited does this for any JavaScript site, with setup pages for React (https://encited.com/prerender-react), Vue (https://encited.com/prerender-vue-spa) and Angular (https://encited.com/prerender-angular).

**TL;DR**

- In React code, dynamic rendering means conditional rendering, component maps, lists and lazy components. All of it runs in the browser.
- Single-page apps in React, Vue and Angular send crawlers an empty `#root`, `#app` or `<app-root>`.
- Googlebot renders them after a delay. GPTBot, ClaudeBot, PerplexityBot and link previews don't render them at all.
- Dynamic rendering serves crawlers the finished page and works the same for all three frameworks.
- You still need history-mode URLs, per-route titles, real 404s and real links.
- Server-rendered setups (Next.js, Nuxt, Angular SSR) still benefit when parts of the page load in the browser.

## React dynamic rendering patterns

In React code, dynamic rendering means deciding at runtime which components appear, based on state, props or data.

### Conditional rendering

Return early for whole-screen states, use a ternary for one of two elements, and `&&` for an optional one:

jsx

Copy Download

function Account ({ user, loading, cart }) {

if (loading) return < Spinner />;

return (

< main >

{ user? < Dashboard user = { user } />: < SignIn /> }

{ cart. items. length > 0 && < CartSummary items = { cart. items } /> }

</ main >

);

}

With three or more cases, map the state to a component instead of nesting ternaries. Use `items.length > 0 &&` rather than `items.length &&`, which renders a stray `0`.

### Component maps from config

When a CMS or config decides the layout, map names to components and pass props through. Typing the map in TypeScript catches a missing component at build time:

tsx

Copy Download

import type { ComponentType } from "react";

type SectionName = "hero" | "pricing" | "faq";

type SectionProps = { title: string };

const SECTIONS: Record < SectionName, ComponentType < SectionProps >> = {

hero: Hero,

pricing: Pricing,

faq: Faq,

};

function Page ({ layout }: { layout: { name: SectionName; title: string } [ ] }) {

return (

< main >

{ layout. map (({ name, title }) => {

const Section = SECTIONS [ name ];

return < Section key = { name } title = { title } />;

}) }

</ main >

);

}

### Lists with `.map()`

Render one component per item, with a stable `key` from your data and the item's fields as props:

jsx

Copy Download

< ul >

{ products. map ((product) => (

< ProductCard key = { product. id } { ... product } />

)) }

</ ul >

### Dynamic styling

Toggle classes or inline styles from state, such as `className={isOpen ? "panel open" : "panel"}`. Content hidden with CSS is still in the HTML crawlers read. Content that isn't rendered at all isn't.

### Lazy components with `React.lazy` and Suspense

Code-split heavy parts of the page so they load on demand:

jsx

Copy Download

const Reviews = lazy (() => import ("./Reviews"));

< Suspense fallback = { < p > Loading reviews </ p > } >

< Reviews productId = { id } />

</ Suspense >

Until the chunk loads, the page holds only the fallback. A snapshot taken too early saves "Loading reviews" instead of the reviews.

### Memoization and virtualization

`memo`, `useMemo` and `useCallback` only change how often React re-renders, so crawlers never notice them. Virtualized lists (`react-window`, `react-virtuoso`) are different: they render only the rows on screen. A crawler, or a snapshot, gets the first screenful of a 500-item list. Give crawlers paginated pages with real links to the rest.

### Vue and Angular dynamic rendering

Vue uses `v-if`, `v-for` and `<component :is>` for the same patterns, plus `defineAsyncComponent` for lazy components. Angular uses `@if`, `@for`, `NgComponentOutlet` and `@defer`.

## Why dynamically rendered pages have SEO and indexing problems?

Every pattern above runs in the browser. None of that output exists until JavaScript runs, so a crawler that reads only the HTML the server sends sees none of the dashboard, sections, product cards or reviews. Googlebot renders the page later, and can still miss content when scripts time out or fail. AI crawlers and link previews never render it.

Dynamic rendering fixes that by serving crawlers a snapshot of the page after it has finished rendering. Encited does this for any React, Vue or Angular app, and serves AI crawlers Markdown as well as HTML.

## What each framework sends crawlers

**React** (Vite, Create React App, most AI website builders):

html

Copy Download

< body >

< div id = " root " > </ div >

< script type = " module " src = " /assets/index-XXXX.js " > </ script >

</ body >

**Vue** (Vite, Vue CLI):

html

Copy Download

< body >

< div id = " app " > </ div >

</ body >

**Angular** (Angular CLI without SSR):

html

Copy Download

< body >

< app-root > </ app-root >

< script src = " main-XXXX.js " type = " module " > </ script >

</ body >

Everything a visitor sees is added by those scripts in the browser. That includes the text, the internal links, the page title and the meta tags you set with React Helmet, `useHead` or Angular's `Title` and `Meta` services.

## Which crawlers render JavaScript

| Crawler | Runs JavaScript? | What it gets from a single-page app |
| --- | --- | --- |
| Googlebot | Yes, in a later pass | The full page, after a delay |
| Bingbot | Partly | Often the full page, less reliably |
| GPTBot, OAI-SearchBot, ChatGPT-User | No | The empty container |
| ClaudeBot | No | The empty container |
| PerplexityBot | No | The empty container |
| LinkedIn, Slack, X, Facebook previews | No | Default title and image, or nothing |

The AI crawler results come from Vercel and MERJ's crawl study (https://vercel.com/blog/the-rise-of-the-ai-crawler). GPTBot and ClaudeBot download JavaScript files but don't execute them.

So a React or Vue site can be indexed in Google and still be blank to ChatGPT and Claude. Shared links show the wrong title because the previews read the raw HTML.

## How dynamic rendering for React SEO works

Dynamic rendering sits in front of your app, at your CDN, your host's edge functions or your DNS. It works the same whichever framework built the page.

1. A request arrives. The rule checks the user agent.
2. If it's a crawler, it gets a snapshot of that URL. The snapshot was made by loading the page in a headless browser, letting React, Vue or Angular finish rendering, and saving the HTML.
3. If it's a person, the request goes to your app as usual.

Because the snapshot is taken after your framework has run, it includes everything the page shows: content loaded from APIs, titles and meta tags set at runtime, structured data and internal links.

With Encited, setup is a DNS change (https://encited.com/docs/pre-rendering/setup-no-code-via-dns) or a middleware snippet for your host, such as Express (https://encited.com/docs/domain-setup/express-js), Next.js (https://encited.com/docs/domain-setup/nextjs) or Vercel (https://encited.com/docs/domain-setup/setup-with-vercel). Nothing in your React, Vue or Angular code changes, and plans start at $19 a month. Snapshots refresh on schedules you set per section of the site, and AI crawlers get a lighter HTML or Markdown version of each page.

Google's documentation now says dynamic rendering was a workaround, and prefers server-side rendering for new builds. It still documents dynamic rendering and doesn't treat it as cloaking when the content matches. The details are in dynamic rendering and SEO (https://encited.com/blog/dynamic-rendering-seo).

## Framework fixes you still need

### Use history-mode URLs

Crawlers treat everything after a `#` as part of the same page. Routes like `/#/pricing` look like one URL to Google.

- **React Router:** use `createBrowserRouter` or `BrowserRouter`.
- **Vue Router:** use `createWebHistory()`. The hash version, `createWebHashHistory()`, keeps every route on one URL.
- **Angular:** keep the default `PathLocationStrategy`, and don't enable `useHash`.

Your host then needs to serve `index.html` for those paths.

### Set a title and description on every route

- **React:** React 19 lets you render `<title>` and `<meta>` inside components and moves them into the head. On older versions, use `react-helmet-async`.
- **Vue:** use `useHead` from `@unhead/vue`.
- **Angular:** set the `title` property on routes, and use the `Meta` service for descriptions and Open Graph tags.

In a client-side app these tags only exist after JavaScript runs. With dynamic rendering, they're in the snapshot every crawler reads.

### Return real 404s

Each framework has a catch-all route for unknown URLs (`path="*"` in React Router, `/:pathMatch(.*)*` in Vue Router, `**` in Angular). Your host still answers those URLs with a 200, which Google files as a soft 404.

The fix is a signal the renderer can read, such as a meta tag the not-found route sets. With Encited, that tag is `<meta name="prerender-status-code" content="404">`, and crawlers then get a 404 status.

### Use real links

Navigate with `<Link>`, `<RouterLink>` or `routerLink`, which render `<a href>` elements. Click handlers on buttons and divs leave crawlers nothing to follow.

### Let data finish loading

If a route fetches data after its first render, a snapshot taken too early misses the content. A good renderer waits until the page settles. For pages with slow or chained API calls, set a ready flag once data has loaded and have the renderer wait for it. Encited waits for `window.prerenderReady = true`.

## If you use Next.js, Nuxt or Angular SSR

Server rendering puts the first version of the page into the HTML. Anything the page fetches in the browser after hydration still isn't there: client-loaded data, deferred sections, tabs, carousels and third-party widgets. Crawlers that read only the first response miss all of it.

Dynamic rendering works on server-rendered apps the same way it works on single-page apps. The snapshot is taken after the page settles, so it includes what hydration and client-side fetches added. AI crawlers can also get a cleaned HTML or Markdown version when the layout is complex. More on how the two fit together in dynamic rendering vs server-side rendering (https://encited.com/blog/dynamic-rendering-vs-server-side-rendering).

## How to check your app

1. Open a route and view the source. An empty `#root`, `#app` or `<app-root>` means crawlers that don't render get nothing.
2. Run `curl -A "GPTBot" https://yoursite.com/some-route` and look for your page text.
3. Use URL Inspection in Search Console to see what Googlebot rendered.
4. Compare Googlebot and AI crawlers side by side in our crawler simulator (https://encited.com/free-tools/crawler-simulator).

## FAQ

**What is dynamic rendering in React?** In React code, it means choosing which components render at runtime with conditions, component maps and lists. In SEO, it means serving crawlers a rendered HTML snapshot of the app, because that runtime output doesn't exist for crawlers that don't run JavaScript.

**How do you dynamically render React components from a config object?** Map the names in your config to imported components in an object, look each one up while mapping over the config, and pass its props through. Type the map in TypeScript so a missing component fails the build.

**How does array mapping work when rendering components in React?** Call `.map()` on the array and return one component per item, with a stable `key` from your data and the item's fields as props.

**How do conditional rendering and dynamic props control what a component shows?** Conditions decide which JSX branch renders: an early return, a ternary, or `&&` for optional parts. Props passed from state or data decide what that branch displays.

**What is the difference between JSX and TSX?** JSX is the HTML-like syntax React uses inside JavaScript files. TSX is the same syntax in TypeScript files, so props and component maps are type-checked.

**Why do dynamically rendered React pages have indexing problems?** The content only exists after JavaScript runs in a browser. Googlebot renders it later and can miss content when scripts time out or fail. AI crawlers and link previews don't render it at all. A rendered snapshot puts the content in the first response.

**Does React need dynamic rendering for SEO?** A client-side React app does if you want AI crawlers and link previews to read it, and to avoid Google's render delay. A Next.js app benefits when content loads in the browser after the first response.

**Is dynamic rendering good for Vue SEO?** Yes, for Vue single-page apps that can't move to Nuxt. It gives crawlers the rendered page without changing your Vue code.

**Does Angular need dynamic rendering if it uses SSR?** Only for content that loads after the first response. `@angular/ssr` puts the initial page in the HTML, and dynamic rendering adds anything fetched or rendered in the browser after hydration.

**Will dynamic rendering break my app for users?** No. People's requests go to your app as before. Only crawlers get the snapshot.

**What about apps built with AI website builders?** Lovable, Bolt, Base44 and similar tools generate React single-page apps, so everything in this post applies. Dynamic rendering is usually the only option, since you can't switch the builder to server rendering.

### Make your React, Vue or Angular app readable to every crawler

Encited serves crawlers the rendered page with no changes to your React, Vue or Angular code.

See how it works: https://encited.com/dynamic-rendering

## Get discovered anywhere search happens

Readable, citable, outranking pages.

Book a demo: https://encited.com/book-a-demo
Get started: https://encited.com/?authModal=true

Image: Avatar (https://replymaven.com/api/uploads/Su6Q0nK63iZW0ZFJBRNWpmRuppVDLuab/98df5262-f992-45fa-a750-00a6c919baf9.png)

How can we help?

Get instant answers to your questions or leave a message for an engineer will reach out

Ask AI about Encited

See our docs (https://lovablehtml.com/docs)

Contact support

Leave a message

We'll get back to you soon

Ask AI about Encited

Product questions, troubleshooting and

Thinking

Image: Preview

Drop an image to attach

Powered by ReplyMaven (https://replymaven.com/)

## Structured data

```json
[
  {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Home",
        "item": " https://encited.com/"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "Blog",
        "item": " https://encited.com/blog"
      },
      {
        "@type": "ListItem",
        "position": 3,
        "name": "Dynamic Rendering in React, Vue and Angular for SEO: Example Implementations and Patterns",
        "item": " https://encited.com/blog/dynamic-rendering-react-vue-angular"
      }
    ]
  },
  {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "Dynamic Rendering in React, Vue and Angular for SEO: Example Implementations and Patterns",
    "description": "Dynamic rendering in React, Vue and Angular: component maps, conditional rendering and lists with examples, and how to keep those pages visible to crawlers.",
    "image": " https://encited.com/blog/thumbnails/dynamic-rendering-react-vue-angular.webp",
    "datePublished": "2026-10-08",
    "dateModified": "2026-10-09",
    "author": {
      "@type": "Person",
      "name": "Aki from Encited"
    },
    "publisher": {
      "@type": "Organization",
      "name": "Encited.com",
      "alternateName": "LovableHTML.com",
      "logo": {
        "@type": "ImageObject",
        "url": "https://encited.com/brand/logo.svg"
      }
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": " https://encited.com/blog/dynamic-rendering-react-vue-angular"
    }
  }
]
```