Building a Vue.js Slide Builder with Edge Delivery Services

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:
- A responsive slide gallery with dynamic image loading
- Interactive modal panels for detailed slide content
- Seamless integration with Edge Delivery Services
- Production-ready Vue.js application
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:
- SlideBuilder: Main container that fetches and organizes slide data
- SlideItem: Individual slide display with image loading and click handling
- SlidePanel: Modal component for detailed slide content viewing
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:
- Composition API: Modern, flexible way to organize component logic
- Reactive References:
slides
automatically updates the UI when data changes - Lifecycle Hooks:
onMounted()
ensures data fetching happens at the right time
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:
- Template Directives:
v-for
efficiently renders dynamic lists - Props Validation: Type-safe component communication
- Clean Component Registration: Simple import and usage pattern
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:
- Returns only the page content without headers, footers, or navigation
- Perfect for loading content into modals or panels
- Maintains all rich content formatting and images
- Enables progressive content loading for better performance
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:
- Automatic WebP Conversion: Serves modern formats when supported
- Dynamic Resizing: Specify width parameters for responsive images
- Quality Optimization: Multiple optimization levels (low, medium, high)
- Format Flexibility: Fallback to JPEG/PNG for older browsers
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:
- Mobile: Essential data only for fast initial render
- Desktop: Progressive enhancement with additional content
- Conditional HTML fetching based on viewport size
Integrating the Vue.js App into Your EDS Application
Method 1: Standalone Page Integration (Recommended)
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:
- Standalone Page: For dedicated slide experiences
- Block Integration: For embedding slides within content pages
Implementation
- Keep the standalone
slides.html
for full-page experiences - Create the block version for inline usage
- Share the same Vue.js bundle and CSS files
- Use conditional mounting logic in your Vue.js app
Testing Your Integration
Verify Deployment
- Check Assets: Ensure CSS and JS files load without 404 errors
- Test Functionality: Click slides, open panels, verify responsive behavior
- 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
- Use EDS analytics to track slide engagement
- Monitor Core Web Vitals for the integrated pages
- Optimize bundle size for faster initial loads
Updating Your Application
Development Workflow
- Make changes to Vue.js source code
- Run
npm run build
to create new production files - Copy updated assets to EDS repository
- Update file references if hash names changed
- 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!
Related Articles