@bagelink/blox

Blox page builder library for drag-and-drop page building and static data management
96,346
Install
npm install @bagelink/blox
Latest Version:1.18.3
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: npm
Maintainer: natiyoung

@bagelink/blox

Blox provides Vue components, routing, previews, and static generation for Bagelink websites.

Installation

pnpm add @bagelink/blox @bagelink/vue vue vue-router

CSS

Every Blox website must import these two stylesheets once, from its client entry (src/main.ts), before project styles:

import '@bagelink/vue/styles/core.css'
import '@bagelink/blox/style.css'
import './styles/main.css'

Use @bagelink/vue/styles/admin.css instead of core.css only for admin applications that need the Bagel font pack.

Do not import @bagelink/vue/dist/style.css or @bagelink/blox/dist/style.css; those internal paths are retained only for existing projects. Import the same entries in local development and CI so a source alias cannot hide a package-export problem.

Production Website Setup

// main.ts
import { createBloxApp } from '@bagelink/blox'
import { BagelVue } from '@bagelink/vue'
import '@bagelink/vue/styles/core.css'
import '@bagelink/blox/style.css'

import App from './App.vue'
import router from './router'

const blocks = import.meta.glob('./components/blocks/*.vue', { eager: true })

createBloxApp({
	rootComponent: App,
	router,
	modules: blocks,
	websiteName: 'my-site',
	store: 'my-site',
	locale: 'en',
	supportedLocales: ['en'],
	plugins: [BagelVue],
	navigation: 'document',
})
// main.server.ts
import { createBloxSSREntry } from '@bagelink/blox/ssg'
import { BagelVue } from '@bagelink/vue'
import App from './App.vue'
import { createRouter } from './router'

const blocks = import.meta.glob('./components/blocks/*.vue', { eager: true })

export const { render, prefetchResolve } = createBloxSSREntry({
	rootComponent: App,
	createRouter,
	modules: blocks,
	websiteName: 'my-site',
	store: 'my-site',
	locale: 'en',
	supportedLocales: ['en'],
	plugins: [BagelVue],
})

The client hydrates prerendered HTML directly. Components can use normal Vue primitives including Suspense, Teleport, Pinia, and onServerPrefetch. Use useData(key, loader) for data that must be present in static HTML and in the hydration payload. Do not install fetch or Axios cache interceptors.

Blox owns the preview route and one named CMS fallback route. Consumer routers should declare app-owned routes and redirects only.

With Custom Components

import { createBlox, createComponentConfig, ButtonConfig } from '@bagelink/blox'
import MyHeroSection from './components/MyHeroSection.vue'

const MyHeroConfig = createComponentConfig({
	id: 'MyHero',
	label: 'Hero Section',
	icon: 'hero_section',
	component: MyHeroSection,
	content: [
		{
			type: 'text',
			key: 'title',
			label: 'Hero Title',
			defaultValue: 'Welcome!',
		},
	],
})

const blox = createBlox()
blox.registerComponents([ButtonConfig, MyHeroConfig]).registerRoutes(router)

app.use(blox)

Available Built-in Component Configs

Import these configs to register built-in components:

  • ButtonConfig - Customizable button/link
  • TextConfig - Rich text content
  • TitleConfig - Heading with optional subtitle
  • ImageConfig - Responsive image
  • SpacerConfig - Vertical spacing
  • ContainerConfig - Content container with max-width

For more examples, see SETUP_EXAMPLE.md.


Low-level Setup

createBlox() remains available for the editor and specialized embedded renderers. New production websites should use createBloxApp() and createBloxSSREntry() so client and server assembly cannot drift.

1. Register Base Components

import { createApp } from 'vue'
import { registerBaseComponents } from '@bagelink/blox'

const app = createApp(App)

// Register all base components
registerBaseComponents(app)

app.mount('#app')

2. Use the RenderPage Component

<template>
	<RenderPage :page-data="pageData" />
</template>

<script setup lang="ts">
import { RenderPage } from '@bagelink/blox'

const pageData = {
  components: [...],
  pageSettings: {...}
}
</script>

3. Use ExternalPreview for Page Builder

<template>
	<ExternalPreview :origin="editorOrigin" />
</template>

<script setup lang="ts">
import { ExternalPreview } from '@bagelink/blox'

const editorOrigin = 'https://your-editor.com'
</script>

Core Concepts

Component Registry

Register your custom components to make them available in the page builder:

import { registerComponent, registerComponents } from '@bagelink/blox'
import MyButton from './MyButton.vue'

// Register a single component
registerComponent('Button', MyButton)

// Or register multiple components at once
registerComponents({
	Button: MyButton,
	Hero: MyHero,
	Text: MyText,
})

Communication System

Blox includes a built-in communication system for coordinating between the builder and preview:

import { createCommunicationBridge } from '@bagelink/blox'

// Create a communication bridge
const bridge = createCommunicationBridge({
	origin: 'https://editor.com',
	targetWindow: window.parent,
})

// Listen for messages
bridge.on('update', ({ data }) => {
	console.log('Received update:', data)
})

// Send messages
bridge.send('ready', { components: ['Button', 'Hero'] })

Styling Utilities

Use the built-in styling utilities to apply consistent styles:

import { generateBlockStyles, getResponsiveClasses } from '@bagelink/blox'

const styles = generateBlockStyles(blockData, false)
const classes = getResponsiveClasses(blockData)

Base Components

The library includes these pre-built components:

  • Button - Customizable button with variants
  • Container - Layout container with spacing
  • Image - Responsive image component
  • Spacer - Vertical/horizontal spacing
  • Text - Rich text content
  • Title - Heading component

API Reference

Registry API

registerComponent(type: string, component: ComponentConstructor)

Register a single component.

import { registerComponent } from '@bagelink/blox'
import MyButton from './MyButton.vue'

registerComponent('Button', MyButton)

Parameters:

  • type - The block type identifier (e.g., 'Button', 'Hero')
  • component - Vue component constructor or async import

registerComponents(components: ComponentRegistry)

Register multiple components at once.

import { registerComponents } from '@bagelink/blox'

registerComponents({
	Button: MyButton,
	Hero: MyHero,
	Text: MyText,
})

Parameters:

  • components - Object mapping type names to components

getComponent(type: string): ComponentConstructor | undefined

Get a registered component by type.

const ButtonComponent = getComponent('Button')

hasComponent(type: string): boolean

Check if a component type is registered.

if (hasComponent('Button')) {
	// Button is registered
}

getAllComponents(): ComponentRegistry

Get all registered components.

const allComponents = getAllComponents()

unregisterComponent(type: string)

Remove a component from the registry.

unregisterComponent('Button')

clearRegistry()

Remove all registered components.

clearRegistry()

getRegisteredTypes(): string[]

Get array of all registered type names.

const types = getRegisteredTypes()
// ['Button', 'Hero', 'Text']

createNamespacedRegistry(namespace: string)

Create a namespaced registry for multi-tenant scenarios.

const siteA = createNamespacedRegistry('site-a')
siteA.register('Button', ButtonA)

const siteB = createNamespacedRegistry('site-b')
siteB.register('Button', ButtonB)

Returns: Object with methods:

  • register(type, component) - Register to namespace
  • get(type) - Get from namespace
  • getAll() - Get all from namespace

Communication API

createCommunicationBridge(config: CommunicationConfig): CommunicationBridge

Create a communication bridge for postMessage communication.

import { createCommunicationBridge } from '@bagelink/blox'

const bridge = createCommunicationBridge({
	origin: 'https://editor.com',
	targetWindow: window.parent,
})

Parameters:

  • config.origin - Allowed origin (string or '*')
  • config.targetWindow - Target window (default: window.parent)
  • config.onMessage - Optional global message handler

CommunicationBridge.on(type: MessageType, handler: MessageHandler): () => void

Register a message handler.

const unsubscribe = bridge.on('update', ({ data }) => {
	console.log('Received update:', data)
})

// Later: unsubscribe()

Parameters:

  • type - Message type or '*' for all messages
  • handler - Handler function

Returns: Unsubscribe function


CommunicationBridge.off(type: MessageType, handler: MessageHandler)

Unregister a message handler.

bridge.off('update', myHandler)

CommunicationBridge.send(type: MessageType, message?: any, data?: any)

Send a message to the target window.

bridge.send('focus', 'block-123')
bridge.send('update', null, { components: [...] })

Parameters:

  • type - Message type
  • message - Optional message payload
  • data - Optional data payload

CommunicationBridge.destroy()

Clean up and remove all listeners.

bridge.destroy()

sendMessage(type, message?, targetWindow?, origin?)

Send a one-off message without creating a bridge.

import { sendMessage } from '@bagelink/blox'

sendMessage('ready', { components: ['Button', 'Hero'] })

Renderer API

initializePage(pageData: PageData): Promise<void>

Initialize a page with all settings and assets.

import { initializePage } from '@bagelink/blox'

await initializePage(pageData)

What it does:

  • Injects responsive CSS
  • Applies page settings
  • Loads Google Fonts
  • Injects custom code

injectResponsiveCSS()

Inject the responsive CSS system.

import { injectResponsiveCSS } from '@bagelink/blox'

injectResponsiveCSS()

injectCode(code: string | undefined, target: 'head' | 'body')

Inject custom code into head or body.

injectCode('<script>console.log("Hello")</script>', 'head')

loadGoogleFont(fontName: string): Promise<void>

Load a Google Font.

await loadGoogleFont('Roboto')

loadComponentFonts(components: ComponentData[]): Promise<void>

Load all fonts used in components.

await loadComponentFonts(pageData.components)

applyGlobalFont(fontName: string)

Apply a font globally to the page.

applyGlobalFont('Inter')

applyPageSettings(pageData: PageData)

Apply all page settings to the document.

applyPageSettings(pageData)

Styles API

generateBlockStyles(data: Record<string, any>, isMobile?: boolean): Record<string, string>

Generate CSS styles from block data.

import { generateBlockStyles } from '@bagelink/blox'

const styles = generateBlockStyles(blockData, false)
// { 'margin-top': '2rem', 'padding': '1rem', ... }

Parameters:

  • data - Block data object
  • isMobile - Whether to use mobile overrides (default: false)

Returns: CSS styles object


getResponsiveClasses(data: Record<string, any>): string[]

Get responsive CSS classes for a block.

const classes = getResponsiveClasses(blockData)
// ['blox-block', 'responsive-colors', 'full-width-mobile']

getResponsiveCSS(): string

Get the responsive CSS to inject.

const css = getResponsiveCSS()

Normalizer API

normalizeComponentData(data: Record<string, any>): Record<string, any>

Normalize component data (convert string booleans, numbers, etc.).

import { normalizeComponentData } from '@bagelink/blox'

const normalized = normalizeComponentData({
	width: '800',
	fullWidth: 'true',
	title: 'Hello',
})
// { width: 800, fullWidth: true, title: 'Hello' }

deepClone<T>(obj: T): T

Deep clone an object.

const cloned = deepClone(originalData)

deepMerge(target: any, source: any): any

Deep merge two objects.

const merged = deepMerge(defaults, customSettings)

Components

ExternalPreview

Preview component for external sites.

<ExternalPreview :origin="editorOrigin" :initial-page-data="pageData" />

Props:

  • origin?: string - Allowed editor origin (default: '*')
  • initialPageData?: PageData - Optional initial page data

Events: Communicates via postMessage with these message types:

  • ready - Sent when preview is ready
  • focus - Sent when block is focused
  • highlight - Sent when block is highlighted

RenderPage

Production render component.

<RenderPage :page-data="pageData" :mobile-breakpoint="910" />

Props:

  • pageData: PageData - Required page data to render
  • mobileBreakpoint?: number - Window width for mobile (default: 910)

TypeScript Types

ComponentData

interface ComponentData {
	id: string
	type: string
	data: Record<string, any>
}

PageData

interface PageData {
	id?: string
	components: ComponentData[]
	pageSettings?: PageSettings
	header_code?: string
	body_code?: string
	language?: Record<string, any>
}

PageSettings

interface PageSettings {
	selectedGoogleFont?: string
	pageLanguage?: string
	pageDirection?: 'ltr' | 'rtl'
	additionalHeadCode?: string
	additionalBodyCode?: string
	customFavicon?: string
	customOgImage?: string
	customKeywords?: string
	customRobotsMeta?: string
	customAnalyticsCode?: string
	disableGlobalHeadCode?: boolean
	disableGlobalBodyCode?: boolean
	disableGoogleAnalytics?: boolean
	disableFacebookPixel?: boolean
}

GlobalSettings

interface GlobalSettings {
	selectedGoogleFont?: string
	pageLanguage?: string
	pageDirection?: 'ltr' | 'rtl'
	globalHeadCode?: string
	globalBodyCode?: string
	favicon?: string
	ogImage?: string
	siteKeywords?: string
	googleAnalyticsId?: string
	facebookPixelId?: string
}

MessageType

type MessageType =
	| 'ready'
	| 'update'
	| 'focus'
	| 'highlight'
	| 'preview'
	| 'meta'
	| 'delete'
	| 'disableLinks'
	| 'newBlock'

EditorMessage

interface EditorMessage {
	type: MessageType
	message?: any
	data?: any
	isMobile?: boolean
}

BlockProps

interface BlockProps {
	isMobile?: boolean
	[key: string]: https://github.com/bageldb/bagelink/blob/HEAD/any
}

StyleConfig

interface StyleConfig {
	// Spacing
	marginTop?: number
	marginBottom?: number
	marginTopMobile?: number
	marginBottomMobile?: number
	padding?: number
	paddingMobile?: number

	// Width
	width?: number
	widthMobile?: number
	widthPercent?: number
	widthPercentMobile?: number
	fullWidth?: boolean
	fullWidthMobile?: boolean

	// Colors
	backgroundColor?: string
	backgroundColorMobile?: string
	textColor?: string
	textColorMobile?: string

	// Border
	borderWidth?: number
	borderWidthMobile?: number
	borderStyle?: string
	borderStyleMobile?: string
	borderColor?: string
	borderColorMobile?: string
	borderRadius?: number
	borderRadiusMobile?: number

	// Typography
	fontFamily?: string
	fontFamilyMobile?: string
	center?: boolean
	centerMobile?: boolean

	// Effects
	shadowType?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'custom'
	shadowTypeMobile?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'custom'
	zIndex?: number
	zIndexMobile?: number

	// Visibility
	showDesktop?: boolean
	showMobile?: boolean

	// Custom
	customId?: string
	customCSS?: string
}

Message Protocol

Editor → Preview

update

Update components data.

{
  type: 'update',
  data: ComponentData[],
  isMobile: boolean
}
focus

Focus a specific block.

{
  type: 'focus',
  message: 'block-id-123'
}
highlight

Highlight a specific block.

{
  type: 'highlight',
  message: 'block-id-123'
}
preview

Toggle preview mode.

{
  type: 'preview',
  message: true | false
}
disableLinks

Disable link navigation.

{
  type: 'disableLinks',
  message: true | false
}

Preview → Editor

ready

Preview is ready.

{
  type: 'ready',
  message: {
    registeredTypes: string[]
  }
}
focus

User focused a block.

{
  type: 'focus',
  message: 'block-id-123'
}
highlight

User highlighted a block.

{
  type: 'highlight',
  message: 'block-id-123'
}
delete

User wants to delete a block.

{
  type: 'delete',
  message: 'block-id-123'
}

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

License

MIT © Bagel Studio

Links

Related Packages

@revenexx/editor

revenexx hard fork of blökkli — interactive page building experience for Nuxt. I...

3,495
lf-pagebuilder-vue

Constructor de páginas visual basado en Vue.js que permite crear y diseñar págin...

18,701
vue-page-composer

Visual page editor and renderer for Vue. Register the components you already shi...

1,731 0
gissen

Headless visual editor and renderer for Vue 3.

1,791 35

Version History

Version Released License
1.18.3 MIT
1.17.6 MIT
1.17.4 MIT
1.17.3 MIT
1.17.2 MIT