@ops-ai/astro-feature-flags-toggly
v1.4.0
Published
Toggly feature flags SDK for Astro with SSR, SSG, and framework support
Readme
Toggly Astro SDK
Feature flag management for Astro applications with support for SSR, SSG, and client-side rendering.
Features
- 🚀 Native Astro Components - Server-rendered
.astrocomponents for optimal performance - 🏝️ Island Architecture - Client-side hydration with Astro islands
- ⚛️ Framework Support - React, Vue, and Svelte component wrappers
- 📄 Page-Level Gating - Control entire pages via frontmatter
- 🔄 SSR & SSG Support - Works seamlessly with both rendering modes
- 🎯 User Targeting - Identity-based feature rollouts
- ⚡ Lightweight - Minimal client bundle using nanostores (~300 bytes)
- 🔌 Edge Ready - Optional Cloudflare Worker integration
Installation
npm install @ops-ai/astro-feature-flags-togglyQuick Start
1. Add the Integration
In your astro.config.mjs:
import { defineConfig } from 'astro/config';
import togglyIntegration from '@ops-ai/astro-feature-flags-toggly/integration';
export default defineConfig({
integrations: [
togglyIntegration({
appKey: process.env.TOGGLY_APP_KEY,
environment: process.env.TOGGLY_ENVIRONMENT || 'Production',
baseURI: 'https://client.toggly.io',
flagDefaults: {
// Fallback values when API is unavailable
'example-feature': false,
},
isDebug: process.env.NODE_ENV === 'development',
}),
],
});2. Configure Middleware
Create or update src/middleware.ts:
import { sequence } from 'astro:middleware';
import { createTogglyMiddleware } from '@ops-ai/astro-feature-flags-toggly';
const toggly = createTogglyMiddleware({
appKey: import.meta.env.TOGGLY_APP_KEY,
environment: import.meta.env.TOGGLY_ENVIRONMENT || 'Production',
});
export const onRequest = sequence(toggly);3. Use the Feature Component
In your .astro files:
---
import Feature from '@ops-ai/astro-feature-flags-toggly/components/Feature.astro';
---
<Feature flag="new-dashboard">
<h1>New Dashboard</h1>
<p>This content is only visible when the feature is enabled</p>
</Feature>Usage
Server-Side Components (Recommended)
Use the Feature.astro component for server-side evaluation (SSR/SSG):
---
import Feature from '@ops-ai/astro-feature-flags-toggly/components/Feature.astro';
---
<!-- Single flag -->
<Feature flag="beta-feature">
<p>Beta content</p>
</Feature>
<!-- Multiple flags with 'all' requirement -->
<Feature flags={['feature1', 'feature2']}>
<p>Both features must be enabled</p>
</Feature>
<!-- Multiple flags with 'any' requirement -->
<Feature flags={['feature1', 'feature2']} requirement="any">
<p>At least one feature must be enabled</p>
</Feature>
<!-- With fallback content -->
<Feature flag="premium-feature">
<p>Premium content</p>
<div slot="fallback">
<p>Upgrade to unlock this feature</p>
</div>
</Feature>
<!-- Negated (show when disabled) -->
<Feature flag="old-feature" negate={true}>
<p>This shows when the feature is OFF</p>
</Feature>Client-Side Components (Islands)
Use FeatureClient.astro for client-side evaluation with hydration:
---
import FeatureClient from '@ops-ai/astro-feature-flags-toggly/components/FeatureClient.astro';
---
<!-- Hydrate on page load -->
<FeatureClient flag="interactive-widget" client="load">
<InteractiveWidget />
</FeatureClient>
<!-- Lazy hydration when visible -->
<FeatureClient flag="below-fold-content" client="visible">
<HeavyComponent />
</FeatureClient>
<!-- Hydrate when browser is idle -->
<FeatureClient flag="non-critical-feature" client="idle">
<NonCriticalContent />
</FeatureClient>Page-Level Gating
Control entire pages using frontmatter:
---
// src/pages/beta-feature.astro
x-feature: beta-feature
---
<html>
<body>
<h1>Beta Feature Page</h1>
<p>This entire page is gated by the 'beta-feature' flag</p>
</body>
</html>During build, the integration generates a toggly-page-features.json manifest that can be used with a Cloudflare Worker for true edge enforcement (404s for disabled pages).
React Integration
Use in React islands:
// Component.tsx
import { Feature, useFeatureFlag } from '@ops-ai/astro-feature-flags-toggly/react';
// With component
export function Dashboard() {
return (
<Feature flag="new-dashboard">
<NewDashboard />
</Feature>
);
}
// With hook
export function ConditionalContent() {
const { enabled, isReady } = useFeatureFlag('premium-feature');
if (!isReady) return <Loading />;
if (!enabled) return <FreeTier />;
return <PremiumTier />;
}In your Astro file:
---
import Dashboard from '../components/Dashboard.tsx';
---
<Dashboard client:load />Vue Integration
<!-- Component.vue -->
<script setup>
import Feature from '@ops-ai/astro-feature-flags-toggly/vue/Feature.vue';
import { useFeatureFlag } from '@ops-ai/astro-feature-flags-toggly/vue';
const { enabled } = useFeatureFlag('new-feature');
</script>
<template>
<Feature flag="beta-widget">
<BetaWidget />
<template #fallback>
<ComingSoon />
</template>
</Feature>
<div v-if="enabled">
<p>Feature-controlled content</p>
</div>
</template>Svelte Integration
<!-- Component.svelte -->
<script>
import Feature from '@ops-ai/astro-feature-flags-toggly/svelte/Feature.svelte';
import { featureFlag } from '@ops-ai/astro-feature-flags-toggly/svelte';
const newDashboard = featureFlag('new-dashboard');
</script>
<Feature flag="beta-feature">
<BetaContent />
<svelte:fragment slot="fallback">
<RegularContent />
</svelte:fragment>
</Feature>
{#if $newDashboard}
<NewDashboard />
{:else}
<OldDashboard />
{/if}Configuration Options
interface TogglyConfig {
/** Base URI for the Toggly API (default: 'https://client.toggly.io') */
baseURI?: string;
/** Application key from Toggly */
appKey?: string;
/** Environment name (default: 'Production') */
environment?: string;
/** Default flag values to use when API is unavailable */
flagDefaults?: Record<string, boolean>;
/** Feature flags refresh interval in milliseconds (default: 180000 = 3 minutes) */
featureFlagsRefreshInterval?: number;
/** Enable debug logging (default: false) */
isDebug?: boolean;
/** Connection timeout in milliseconds (default: 5000) */
connectTimeout?: number;
/** User identity for targeting (optional) */
identity?: string;
/**
* When true, all features are enabled during build time (SSG).
* This is useful when you have an edge worker (like Cloudflare Worker) that
* filters content based on feature flags at runtime.
* During dev server, actual feature flags from the API are still used.
* (default: false)
*/
allFeaturesEnabledDuringBuild?: boolean;
}Build Mode for Edge Filtering
If you're using an edge worker (Cloudflare Worker, Vercel Edge, etc.) to filter content based on feature flags at runtime, you can enable all features during the static build:
// astro.config.mjs
const isDev = process.env.NODE_ENV === 'development';
const isBuild = process.argv.includes('build');
export default defineConfig({
integrations: [
togglyIntegration({
appKey: process.env.TOGGLY_APP_KEY,
environment: process.env.TOGGLY_ENVIRONMENT || 'Production',
baseURI: 'https://client.toggly.io',
// Enable all features during production builds
// Use actual feature flags during development
allFeaturesEnabledDuringBuild: isBuild && !isDev,
isDebug: isDev,
}),
],
});Benefits:
- SEO: All feature-flagged content is present in the static build for search engines
- No broken links: Features disabled during build won't cause broken internal links
- Edge performance: Static build generated once, edge worker does lightweight filtering
- Dynamic control: Toggle features at the edge without rebuilding
- Dev experience: See actual feature states during local development
How it works:
- Development (
npm run dev): Fetches actual feature flags from Toggly API - Build (
npm run build): Builds static site with all features enabled - Runtime (Edge/CDN): Edge worker filters content based on current feature flag states
SSR vs SSG Considerations
SSR (Server-Side Rendering)
- Flags are fetched on each request
- Fresh flag values for every visitor
- Slightly slower initial page load
- Use
output: 'server'inastro.config.mjs
export default defineConfig({
output: 'server',
// ...
});SSG (Static Site Generation)
- Flags are fetched at build time
- Same flag values for all visitors
- Fastest possible page loads
- Requires rebuild to update flags
- Use client-side components for dynamic updates
export default defineConfig({
output: 'static',
// ...
});Hybrid Approach
Use server components for critical gating and client components for non-critical features:
---
import Feature from '@ops-ai/astro-feature-flags-toggly/components/Feature.astro';
import FeatureClient from '@ops-ai/astro-feature-flags-toggly/components/FeatureClient.astro';
---
<!-- Critical feature - evaluated at build/request time -->
<Feature flag="access-control">
<SecureContent />
</Feature>
<!-- Non-critical feature - evaluated on client -->
<FeatureClient flag="ui-enhancement" client="idle">
<EnhancedUI />
</FeatureClient>Advanced Usage
User Identity for Targeting
// In middleware or component
import { setIdentity } from '@ops-ai/astro-feature-flags-toggly';
// Set user identity for targeting
setIdentity('user-123');
// Clear identity (e.g., on logout)
import { clearIdentity } from '@ops-ai/astro-feature-flags-toggly';
clearIdentity();Manual Flag Refresh
import { refreshFlags } from '@ops-ai/astro-feature-flags-toggly';
// Manually refresh flags
await refreshFlags();Programmatic Flag Evaluation
In server-side code:
const toggly = Astro.locals.toggly;
const isEnabled = await toggly.getFlag('feature-key');
const allFlags = await toggly.getFlags();Edge Enforcement (Optional)
For true enforcement at the edge (prevents page access even if client-side JS is disabled):
- The integration generates
toggly-page-features.jsonduring build - Deploy a Cloudflare Worker that reads this manifest
- The worker intercepts requests and returns 404 for disabled pages
See the Cloudflare Worker integration guide for setup instructions.
TypeScript Support
Full TypeScript support is included:
import type {
TogglyConfig,
Flags,
TogglyClient,
FeatureProps,
} from '@ops-ai/astro-feature-flags-toggly';
// Augmented Astro global
Astro.locals.toggly; // Typed as TogglyClientBest Practices
- Use Server Components When Possible - Better performance, no client-side JavaScript
- Set Flag Defaults - Always provide fallback values for offline scenarios
- Use Environment Variables - Never hardcode credentials
- Enable Debug Mode in Development - Helps troubleshoot issues
- Cache Appropriately - Adjust
featureFlagsRefreshIntervalbased on your needs - Provide User Identity - Required for targeting and consistent rollouts
- Consider SSR vs SSG - Choose based on how dynamic your flags need to be
- Use Page-Level Gating - For entire pages, use frontmatter instead of wrapping all content
Troubleshooting
Flags not loading
- Check that the integration is properly configured in
astro.config.mjs - Verify
appKeyandenvironmentare correct - Enable debug mode:
isDebug: true - Check browser console and server logs
TypeScript errors
Make sure you have the necessary dependencies:
npm install -D @types/node astroClient components not hydrating
Ensure you're using the correct hydration directive:
<FeatureClient flag="test" client="load">
<!-- content -->
</FeatureClient>Middleware not working
Verify middleware is properly configured in src/middleware.ts and exported as onRequest.
Examples
Check the examples/ directory for complete working examples:
- Basic Astro + Toggly setup
- SSR with feature flags
- SSG with client-side updates
- React/Vue/Svelte islands
License
MIT
Support
Related
- @ops-ai/toggly-docusaurus-plugin - Docusaurus integration
- @ops-ai/react-feature-flags-toggly - React SDK
- @ops-ai/feature-flags-toggly - Vanilla JavaScript SDK
Extensibility with Hooks
Toggly provides a powerful hooks system that allows you to extend SDK functionality by hooking into feature flag lifecycle events. This is perfect for integrating with analytics, monitoring tools, or implementing custom behaviors.
What are Hooks?
Hooks let you execute custom code at specific points in the feature flag evaluation lifecycle:
- beforeEvaluation: Called before a feature flag is evaluated
- afterEvaluation: Called after a feature flag is evaluated (with the result)
- beforeIdentify: Called before user identity is set or cleared
- afterIdentify: Called after user identity is set or cleared
- afterRefresh: Called after feature definitions are refreshed from Toggly
Creating a Hook
import type { Hook } from '@ops-ai/toggly-hooks-types';
const myAnalyticsHook: Hook = {
getMetadata: () => ({
name: 'MyAnalyticsHook',
version: '1.0.0'
}),
afterEvaluation: async (data) => {
// Send to analytics
analytics.track('Feature Flag Evaluated', {
feature: data.featureKey,
enabled: data.result
});
}
};Registering Hooks
During initialization in astro.config:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import toggly from '@ops-ai/astro-feature-flags-toggly';
export default defineConfig({
integrations: [
toggly({
appKey: 'your-app-key',
environment: 'your-environment-name',
hooks: [myAnalyticsHook]
})
]
});At runtime (client-side):
import { togglyStore } from '@ops-ai/astro-feature-flags-toggly/client';
import { get } from 'svelte/store';
const store = get(togglyStore);
// Add a hook
store.hookExecutor.addHook(myAnalyticsHook);
// Remove a hook
store.hookExecutor.removeHook(myAnalyticsHook);Common Use Cases
Analytics Integration:
const clarityHook: Hook = {
getMetadata: () => ({ name: 'Microsoft Clarity', version: '1.0.0' }),
afterEvaluation: async (data) => {
if (typeof clarity !== 'undefined') {
clarity('event', `FeatureFlag:${data.featureKey}`);
}
}
};Debug Logging:
const debugHook: Hook = {
getMetadata: () => ({ name: 'DebugLogger', version: '1.0.0' }),
afterEvaluation: async (data) => {
if (import.meta.env.DEV) {
console.debug('[Toggly]', data.featureKey, '=', data.result);
}
}
};Related SDKs
- @ops-ai/react-feature-flags-toggly - React SDK
- @ops-ai/feature-flags-toggly - Vanilla JavaScript SDK
