Building a Vue.js Slide Builder with Edge Delivery Services

Author: Tom Cranstoun
Create an interactive, responsive slide viewer application using Vue.js and Adobe Edge Delivery Services as your content management system.

You may already have a complex Vue.js application which your client wishes to include in EDS, either as a separate page or as a block within a page. This tutorial will show you how to create a vue.js app or integrate an existing one.

📂 Complete Code Available: All the code from this tutorial is available in the vue-with-eds GitHub repository.

Project Overview

This tutorial will guide you through building a Vue.js slide builder application that integrates seamlessly with Adobe Edge Delivery Services. The application fetches slide content dynamically and provides an interactive viewing experience with modal panels for detailed content.

We will use a pre-existing piece of json built using EDS for this example taken from my blog post https://allabout.network/blogs/ddt/integrations/building-headless-applications-with-edge-delivery-services

What You'll Build:

Why Vue.js for Edge Delivery Services?

Vue.js offers several advantages for Edge Delivery Services development:

Template Clarity: Vue's template syntax provides clean, readable markup Reactive State: Automatic UI updates when data changes Gentle Learning Curve: Familiar HTML-like syntax with powerful features Excellent Tooling: Great developer experience with Vite and Vue DevTools Modular Architecture: Easy to create reusable, maintainable components

Application Architecture

Our slide builder consists of three main components:

Setting Up the Vue.js Project

💡 Quick Setup: You can clone the complete project structure with: git clone https://github.com/ddttom/vue-with-eds.git

Start by creating a new Vue.js project using Vite for optimal performance:

# Create new Vue.js project
npm create vue@latest vue-with-eds
cd vue-with-eds
npm install

Project Structure

Organize your project with clear separation of concerns:

/vue-with-eds
├── src
│   ├── App.vue
│   ├── components
│   │   ├── SlideBuilder.vue
│   │   ├── SlideItem.vue
│   │   └── SlidePanel.vue
│   ├── main.js
│   ├── style.css
│   └── styles
│       ├── SlideBuilder.css
│       ├── SlideItem.css
│       └── SlidePanel.css
└── vite.config.js

Building the Components

App.vue - Main Application

The root component handles data fetching and provides the application structure:

<template>
  <div class="app">
    <main>
      <SlideBuilder :slides="slides" />
    </main>
  </div>
</template>
<script>

import { ref, onMounted } from 'vue'
import SlideBuilder from './components/SlideBuilder.vue'
export default {
  name: 'App',
  components: { SlideBuilder },
  setup() {
    const slides = ref([])
    const fetchSlides = async () => {
      try {
        const response = await fetch("/slides/query-index.json")
        const json = await response.json()
        slides.value = json.data
      } catch (error) {
        console.error("Failed to fetch slides:", error)
      }
    }

    onMounted(() => {
      fetchSlides()
    })
    return { slides }
  }
}
</script>

Key Vue.js Features:

SlideBuilder.vue - Container Component

The container component renders the slide collection:

<template>
  <div class="slide-builder">
    <SlideItem 
      v-for="(slide, index) in slides" 
      :key="slide.path" 
      :slide-data="slide" 
      :index="index" 
    />
  </div>
</template>
<script>
import SlideItem from './SlideItem.vue'
export default {
  name: 'SlideBuilder',
  components: { SlideItem },
  props: {
    slides: {
      type: Array,
      required: true
    }
  }
}
</script>

Vue.js Features Highlighted:

SlideItem.vue - Interactive Slide Component

Each slide handles its own state, image loading, and content fetching:

<template>
  <div>
    <div
      class="slide-builder-item"
      :style="{ backgroundImage: `url(${bgImage})` }"
      :data-slidenum="index + 1"
      @click="showPanel = true"
    >

      <div class="text-container">
        <h2>{{ slideData.title }}</h2>
        <p><strong>{{ slideData.description }}</strong></p>
        <p v-if="supportingText" class="supporting-text">
          {{ supportingText }}
        </p>
      </div>
    </div>
    <SlidePanel 
      v-if="showPanel"
      :slide-data="slideData" 
      @close="showPanel = false" 
    />
  </div>

</template>
<script>
import { ref, onMounted, watch } from 'vue'
import SlidePanel from './SlidePanel.vue'
export default {
  name: 'SlideItem',
  components: { SlidePanel },
  props: {
    slideData: { type: Object, required: true },
    index: { type: Number, required: true }
  },

  setup(props) {
    const bgImage = ref('')
    const supportingText = ref('')
    const showPanel = ref(false)
    // Check for WebP support for optimal image delivery
    const supportsWebP = window.createImageBitmap && 
      window.createImageBitmap.toString().includes("native code")
    const setSlideBackground = () => {
      const imageUrl = props.slideData.image.split("?")[0]
      const finalImageUrl = supportsWebP
        ? `${imageUrl}?width=2000&format=webply&optimize=medium`
        : `${imageUrl}?width=2000&optimize=medium`
      const img = new Image()
      img.onload = () => bgImage.value = finalImageUrl
      img.onerror = () => console.error(`Failed to load image: ${finalImageUrl}`)
      img.src = finalImageUrl
    }

    const fetchSupportingText = async () => {
      // Only fetch supporting text on desktop for performance
      if (window.innerWidth <= 799) return
      try {
        const response = await fetch(`${props.slideData.path}.plain.html`)
        if (!response.ok) return
        const html = await response.text()
        const parser = new DOMParser()
        const doc = parser.parseFromString(html, "text/html")
        // Extract first paragraph after h2
        const h2 = doc.querySelector("h2")
        let firstParagraph = h2 ? h2.nextElementSibling : doc.querySelector("p")
        while (firstParagraph && firstParagraph.tagName.toLowerCase() !== "p") {
          firstParagraph = firstParagraph.nextElementSibling
        }
        supportingText.value = firstParagraph?.textContent.trim() || null
      } catch (error) {
        console.error('Failed to fetch supporting text:', error)
      }
    }

    onMounted(() => {
      setSlideBackground()
      fetchSupportingText()
    })

    watch(() => props.slideData, () => {
      setSlideBackground()
      fetchSupportingText()
    })
    return { bgImage, supportingText, showPanel }
  }
}
</script>

📋 Complete Implementation: See the full SlideItem.vue for additional features like dynamic text fetching and responsive behavior.

Component Communication Patterns

Vue.js provides elegant patterns for component communication:

Parent to Child (Props)

<SlideItem :slide-data="slide" :index="index" />

Child to Parent (Events)

<!-- In SlidePanel.vue -->
<button @click="$emit('close')">Close</button>
<!-- In SlideItem.vue -->
<SlidePanel @close="showPanel = false" />

Reactive State Management

// Automatic UI updates when state changes
const showPanel = ref(false)
showPanel.value = true // UI automatically updates

Styling Architecture

Vue.js supports multiple styling approaches. We use modular CSS files for maintainability:

<script>
import '../styles/SlideItem.css'
</script>

The modular approach allows each component to manage its own styles while sharing common design patterns.

🎨 Complete Styles: All CSS files are available in the styles directory.

Edge Delivery Services Integration

Setting Up Your Content Source

Before building the Vue.js application, you need to create the content structure in Edge Delivery Services. This involves creating individual slide pages and setting up a query index to expose them as JSON data.

There is a full blog post at https://allabout.network/blogs/ddt/integrations/building-headless-applications-with-edge-delivery-services which details how to set up the data source

Once configured, Edge Delivery Services automatically generates JSON data at https://allabout.network/slides/query-index.json

{
  "total": 5,
  "offset": 0,
  "limit": 5,
  "data": [
    {
      "path": "/slides/york-minster",
      "title": "York Minster",
      "image": "/slides/media_188fa5bcd003e5a2d56e7ad3ca233300c9e52f1e5.png?width=1200&format=pjpg&optimize=medium",
      "description": "A magnificent Gothic cathedral with centuries of history and breathtaking architecture",
      "lastModified": "1719573871"
    },

    {
      "path": "/slides/the-shambles",
      "title": "The Shambles",
      "image": "/slides/media_1b0dfc9893bff97d7d5043efd4e51ba659d6279fd.png?width=1200&format=pjpg&optimize=medium",
      "description": "A picturesque medieval street with overhanging buildings and quaint shops.",
      "lastModified": "1719574976"
    }
  ],
  "type": "sheet"
}

Dynamic Content Loading with .plain.html

Edge Delivery Services provides a powerful feature for fetching page content as fragments using the .plain.html suffix:

const fetchSlideHtml = async (path) => {
  try {
    const response = await fetch(`${path}.plain.html`)
    if (!response.ok) throw new Error(`Failed to fetch HTML for slide: ${path}`)
    return await response.text()
  } catch (error) {
    console.error(error)
    return null
  }
}

Key Benefits of .plain.html:

Content Strategy for Performance

The application implements a mobile-first, desktop-enriched strategy:

Mobile Approach: Load minimal data initially for fast rendering

if (window.innerWidth <= 799) {
  slide.html = null // Skip detailed content on mobile
}

Desktop Enhancement: Load additional content for richer experience

if (window.innerWidth > 799) {
  slide.html = await fetchSlideHtml(slide.path)
}

Extracting Supporting Text

The application can automatically extract preview text from your Edge Delivery Services pages:

const extractSupportingText = (html) => {
  const parser = new DOMParser()
  const doc = parser.parseFromString(html, "text/html")
  const h2 = doc.querySelector("h2")
  let firstParagraph = h2 ? h2.nextElementSibling : doc.querySelector("p")
  while (firstParagraph && firstParagraph.tagName.toLowerCase() !== "p") {
    firstParagraph = firstParagraph.nextElementSibling
  }
  return firstParagraph?.textContent.trim() || null
}

Optimized Image Delivery

Edge Delivery Services provides automatic image optimization. The Vue.js application leverages this with intelligent format detection:

// Check for WebP support
const supportsWebP = window.createImageBitmap && 
  window.createImageBitmap.toString().includes("native code")
// Construct optimized image URL
const finalImageUrl = supportsWebP
  ? `${imageUrl}?width=2000&format=webply&optimize=medium`
  : `${imageUrl}?width=2000&optimize=medium`

Image Optimization Features:

Performance Considerations

The application implements several performance optimizations:

Lazy Loading with Intersection Observer:

const observer = new IntersectionObserver( 
 (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        loadSlideImage(entry.target)
      }
    })
  },
  { rootMargin: "100px" }
)

Responsive Content Strategy:

Integrating the Vue.js App into Your EDS Application

This approach deploys your Vue.js app as a standalone page within your EDS site.

Step 1: Build for Production

npm run build

Step 2: Prepare Your EDS Repository

Navigate to your existing Edge Delivery Services repository and create the necessary structure, then copy built files:

cd your-eds-repository
 
# Copy assets
cp ../vue-with-eds/dist/assets/* ./assets
Cp ../vue-with-eds/dist/index.html ./vue-slides.html
 

Step 5: Deploy

git add .
git commit -m "Add Vue.js slide builder application"
git push

Your Vue.js app will be available at https://your-domain.com/vue-slides.html

My Vue.js app is here : https://allabout.network/vue-slides.html


Method 2: Block Integration

For deeper integration where the Vue.js app appears as a block within EDS pages.

Step 1: Create Block Structure

mkdir -p blocks/vue-slide-builder

Step 2: Create Block Files

blocks/vue-slide-builder/vue-slide-builder.js:

export default function decorate(block) {
    // Create container for Vue.js app
    const container = document.createElement('div');
    container.id = 'vue-slide-app';
    block.appendChild(container);
  
    // Load Vue.js bundle
    const script = document.createElement('script');
    script.src = '/assets/index-UbQ-77Ai.js'; // Your built JS file
    script.type = 'module';
    document.head.appendChild(script);
    
    // Load CSS
  
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = '/assets/index-DMC9YjsP.css'; // Your built CSS file
    document.head.appendChild(link);
  }
 
REMEMBER TO CHANGE HASHES

blocks/vue-slide-builder/vue-slide-builder.css:

.vue-slide-builder {
  width: 100%;
  min-height: 400px;
}

.vue-slide-builder #vue-slide-app {
  width: 100%;
}

Step 3: Modify Vue.js Mount Point

Update your Vue.js main.js to check for the block container:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// Look for block container first, fallback to #app
const mountPoint = document.getElementById('vue-slide-app') || 
                   document.getElementById('app')
if (mountPoint) {
  createApp(App).mount(mountPoint)
}

Step 4: Use in Documents

In your Google Docs, add the block:

Vue Slide Builder
| vue-slide-builder |
|                   |

I have used the block here: https://allabout.network/blogs/ddt/integrations/vue-js-version-of-slide-viewer-in-gdoc

Method 3: Hybrid Approach (Best of Both Worlds)

Combine both methods for maximum flexibility:

  1. Standalone Page: For dedicated slide experiences
  2. Block Integration: For embedding slides within content pages

Implementation

Testing Your Integration

Verify Deployment

  1. Check Assets: Ensure CSS and JS files load without 404 errors
  2. Test Functionality: Click slides, open panels, verify responsive behavior
  3. Cross-Browser: Test in different browsers and devices

Production Optimization

EDS-Specific Optimizations

// Leverage EDS image optimization
const optimizeImageUrl = (url, width = 1200) => {
  const baseUrl = url.split('?')[0]
  return `${baseUrl}?width=${width}&format=webply&optimize=medium`
}

// Respect EDS fragment loading

const loadContent = async (path) => {
  const response = await fetch(`${path}.plain.html`)
  return response.text()
}

Performance Monitoring

Updating Your Application

Development Workflow

  1. Make changes to Vue.js source code
  2. Run npm run build to create new production files
  3. Copy updated assets to EDS repository
  4. Update file references if hash names changed
  5. Commit and deploy

Development Experience Benefits

Vue.js Advantages

Intuitive Syntax: Template directives feel natural and readable

<div v-if="loading">Loading...</div>
<SlideBuilder v-else :slides="slides" />

Reactive State: No complex state management patterns needed

const isVisible = ref(false)
isVisible.value = true // UI automatically updates

Excellent Tooling: Vue DevTools provide comprehensive debugging Hot Reload: Instant feedback during development TypeScript Support: Optional static typing for larger projects

Conclusion

Building a Vue.js slide builder with Edge Delivery Services demonstrates the power of combining modern frontend frameworks with headless content management. This approach provides:

Developer Productivity: Intuitive Vue.js patterns accelerate development Content Flexibility: Authors work in familiar document environments Performance: Optimized builds and progressive loading Maintainability: Clear component structure and separation of concerns Scalability: Easy to extend with additional features and integrations

The Vue.js ecosystem offers excellent tooling and community support, making it an ideal choice for Edge Delivery Services applications that require interactive, dynamic user interfaces.

🔗 Get Started: Clone the complete project and customize it for your needs!

This pattern works excellently for portfolios, product showcases, educational content, marketing campaigns, and any scenario where you need rich, interactive content management with a modern development experience.

Resources and Next Steps

Vue.js Documentation: vuejs.org Edge Delivery Services: Adobe Edge Delivery Services Vite Build Tool: vitejs.dev Project Repository: vue-with-eds

About the Author

Tom Cranstoun specializes in modern web development and content management solutions. Digital Domain Technologies Ltd provides CMS consultancy with 13+ years of experience helping organizations implement robust, scalable web solutions.

For expert guidance on Vue.js applications or Edge Delivery Services integration, contact: info@digitaldomaintechnologies.com

Found this helpful? Star the vue-with-eds repository on GitHub!

/fragments/ddt/proposition

Related Articles

path=*
path=*
Back to Top