CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue--runtime-core

Vue.js 3 runtime core library providing foundational APIs for building custom renderers and managing reactive component systems

Pending
Overview
Eval results
Files

ssr-context.mddocs/

SSR Context

Vue's Server-Side Rendering (SSR) context system enables sharing data between server and client during the hydration process, supporting universal applications with server-rendered initial state.

Capabilities

SSR Context Access

Access and provide SSR context data during server-side rendering.

/**
 * Gets the current SSR context (only available during SSR)
 * @returns SSR context object or undefined on client
 */
function useSSRContext<T = Record<string, any>>(): T | undefined;

/**
 * Injection key for SSR context
 */
const ssrContextKey: InjectionKey<Record<string, any>>;

Usage Examples:

import { defineComponent, useSSRContext, ssrContextKey, inject } from "@vue/runtime-core";

// Basic SSR context usage
const SSRComponent = defineComponent({
  setup() {
    const ssrContext = useSSRContext();
    
    if (ssrContext) {
      // We're on the server
      console.log('Rendering on server');
      
      // Add data to SSR context
      ssrContext.title = 'My App - Home Page';
      ssrContext.meta = {
        description: 'Welcome to my application',
        keywords: 'vue, ssr, typescript'
      };
      
      // Add CSS files to be included
      ssrContext.css = ssrContext.css || [];
      ssrContext.css.push('/styles/home.css');
      
      // Add preload hints
      ssrContext.preloadLinks = ssrContext.preloadLinks || [];
      ssrContext.preloadLinks.push({
        href: '/api/data',
        as: 'fetch',
        crossorigin: 'anonymous'
      });
    } else {
      // We're on the client
      console.log('Hydrating on client');
    }
    
    return {};
  }
});

// Using injection key for SSR context
const InjectionComponent = defineComponent({
  setup() {
    const ssrContext = inject(ssrContextKey);
    
    if (ssrContext) {
      // Server-side logic
      ssrContext.userData = {
        timestamp: Date.now(),
        userAgent: 'server'
      };
    }
    
    return {};
  }
});

// Conditional rendering based on SSR
const UniversalComponent = defineComponent({
  setup() {
    const isSSR = !!useSSRContext();
    
    return () => {
      if (isSSR) {
        return h('div', 'Server-rendered content');
      } else {
        return h('div', 'Client-rendered content');
      }
    };
  }
});

SSR Data Management

Manage data transfer between server and client rendering.

// SSR data store
const useSSRDataStore = () => {
  const ssrContext = useSSRContext();
  
  const setSSRData = (key: string, data: any) => {
    if (ssrContext) {
      ssrContext.state = ssrContext.state || {};
      ssrContext.state[key] = data;
    }
  };
  
  const getSSRData = (key: string, defaultValue: any = null) => {
    if (ssrContext?.state) {
      return ssrContext.state[key] ?? defaultValue;
    }
    
    // On client, try to get from window.__SSR_STATE__
    if (typeof window !== 'undefined' && window.__SSR_STATE__) {
      return window.__SSR_STATE__[key] ?? defaultValue;
    }
    
    return defaultValue;
  };
  
  return {
    setSSRData,
    getSSRData,
    isSSR: !!ssrContext
  };
};

// Usage in components
const DataComponent = defineComponent({
  async setup() {
    const { setSSRData, getSSRData, isSSR } = useSSRDataStore();
    const data = ref(null);
    
    if (isSSR) {
      // Fetch data on server
      try {
        const response = await fetch('https://api.example.com/data');
        const serverData = await response.json();
        
        data.value = serverData;
        setSSRData('componentData', serverData);
      } catch (error) {
        console.error('SSR data fetch failed:', error);
        setSSRData('componentData', null);
      }
    } else {
      // Get data from SSR state on client
      data.value = getSSRData('componentData');
      
      // If no SSR data, fetch on client
      if (!data.value) {
        const response = await fetch('https://api.example.com/data');
        data.value = await response.json();
      }
    }
    
    return { data };
  }
});

SSR Metadata Management

Manage document metadata during server-side rendering.

// Head management composable
const useSSRHead = () => {
  const ssrContext = useSSRContext();
  
  const setTitle = (title: string) => {
    if (ssrContext) {
      ssrContext.title = title;
    } else if (typeof document !== 'undefined') {
      document.title = title;
    }
  };
  
  const addMeta = (meta: { name?: string; property?: string; content: string }) => {
    if (ssrContext) {
      ssrContext.meta = ssrContext.meta || [];
      ssrContext.meta.push(meta);
    } else if (typeof document !== 'undefined') {
      const metaElement = document.createElement('meta');
      if (meta.name) metaElement.name = meta.name;
      if (meta.property) metaElement.setAttribute('property', meta.property);
      metaElement.content = meta.content;
      document.head.appendChild(metaElement);
    }
  };
  
  const addLink = (link: { rel: string; href: string; [key: string]: string }) => {
    if (ssrContext) {
      ssrContext.links = ssrContext.links || [];
      ssrContext.links.push(link);
    } else if (typeof document !== 'undefined') {
      const linkElement = document.createElement('link');
      Object.entries(link).forEach(([key, value]) => {
        linkElement.setAttribute(key, value);
      });
      document.head.appendChild(linkElement);
    }
  };
  
  const addScript = (script: { src?: string; innerHTML?: string; [key: string]: any }) => {
    if (ssrContext) {
      ssrContext.scripts = ssrContext.scripts || [];
      ssrContext.scripts.push(script);
    } else if (typeof document !== 'undefined') {
      const scriptElement = document.createElement('script');
      if (script.src) scriptElement.src = script.src;
      if (script.innerHTML) scriptElement.innerHTML = script.innerHTML;
      
      Object.entries(script).forEach(([key, value]) => {
        if (key !== 'src' && key !== 'innerHTML') {
          scriptElement.setAttribute(key, value);
        }
      });
      
      document.head.appendChild(scriptElement);
    }
  };
  
  return {
    setTitle,
    addMeta,
    addLink,
    addScript
  };
};

// SEO component
const SEOComponent = defineComponent({
  props: {
    title: String,
    description: String,
    keywords: String,
    image: String,
    url: String
  },
  
  setup(props) {
    const { setTitle, addMeta, addLink } = useSSRHead();
    
    // Set page title
    if (props.title) {
      setTitle(props.title);
    }
    
    // Basic meta tags
    if (props.description) {
      addMeta({ name: 'description', content: props.description });
    }
    
    if (props.keywords) {
      addMeta({ name: 'keywords', content: props.keywords });
    }
    
    // Open Graph meta tags
    if (props.title) {
      addMeta({ property: 'og:title', content: props.title });
    }
    
    if (props.description) {
      addMeta({ property: 'og:description', content: props.description });
    }
    
    if (props.image) {
      addMeta({ property: 'og:image', content: props.image });
    }
    
    if (props.url) {
      addMeta({ property: 'og:url', content: props.url });
      addLink({ rel: 'canonical', href: props.url });
    }
    
    // Twitter Card meta tags
    addMeta({ name: 'twitter:card', content: 'summary_large_image' });
    
    if (props.title) {
      addMeta({ name: 'twitter:title', content: props.title });
    }
    
    if (props.description) {
      addMeta({ name: 'twitter:description', content: props.description });
    }
    
    if (props.image) {
      addMeta({ name: 'twitter:image', content: props.image });
    }
    
    return () => null; // This component doesn't render anything
  }
});

SSR Performance Optimization

Optimize performance with resource hints and preloading.

// Resource preloading
const useSSRPreload = () => {
  const ssrContext = useSSRContext();
  
  const preloadResource = (href: string, as: string, crossorigin?: string) => {
    if (ssrContext) {
      ssrContext.preloadLinks = ssrContext.preloadLinks || [];
      ssrContext.preloadLinks.push({
        rel: 'preload',
        href,
        as,
        ...(crossorigin && { crossorigin })
      });
    } else if (typeof document !== 'undefined') {
      const link = document.createElement('link');
      link.rel = 'preload';
      link.href = href;
      link.as = as;
      if (crossorigin) link.crossOrigin = crossorigin;
      document.head.appendChild(link);
    }
  };
  
  const prefetchResource = (href: string) => {
    if (ssrContext) {
      ssrContext.prefetchLinks = ssrContext.prefetchLinks || [];
      ssrContext.prefetchLinks.push({
        rel: 'prefetch',
        href
      });
    } else if (typeof document !== 'undefined') {
      const link = document.createElement('link');
      link.rel = 'prefetch';
      link.href = href;
      document.head.appendChild(link);
    }
  };
  
  return {
    preloadResource,
    prefetchResource
  };
};

// Critical CSS management
const useSSRCriticalCSS = () => {
  const ssrContext = useSSRContext();
  
  const addCriticalCSS = (css: string) => {
    if (ssrContext) {
      ssrContext.criticalCSS = ssrContext.criticalCSS || [];
      ssrContext.criticalCSS.push(css);
    }
  };
  
  const addStylesheet = (href: string, media = 'all') => {
    if (ssrContext) {
      ssrContext.stylesheets = ssrContext.stylesheets || [];
      ssrContext.stylesheets.push({ href, media });
    } else if (typeof document !== 'undefined') {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = href;
      link.media = media;
      document.head.appendChild(link);
    }
  };
  
  return {
    addCriticalCSS,
    addStylesheet
  };
};

Advanced SSR Patterns

// SSR-safe component wrapper
const createSSRSafeComponent = <P extends Record<string, any>>(
  component: Component<P>,
  fallback?: Component<P>
) => {
  return defineComponent({
    props: component.props,
    setup(props, ctx) {
      const isSSR = !!useSSRContext();
      
      if (isSSR && fallback) {
        return () => h(fallback, props);
      }
      
      return () => h(component, props);
    }
  });
};

// Client-only component
const ClientOnly = defineComponent({
  setup(_, { slots }) {
    const isSSR = !!useSSRContext();
    const isMounted = ref(false);
    
    onMounted(() => {
      isMounted.value = true;
    });
    
    return () => {
      if (isSSR || !isMounted.value) {
        return slots.fallback?.() || null;
      }
      return slots.default?.();
    };
  }
});

// SSR context provider
const SSRContextProvider = defineComponent({
  props: {
    context: { type: Object, required: true }
  },
  
  setup(props, { slots }) {
    provide(ssrContextKey, props.context);
    
    return () => slots.default?.();
  }
});

// Universal data fetching
const useUniversalFetch = <T>(
  url: string,
  options: RequestInit = {}
): { data: Ref<T | null>; loading: Ref<boolean>; error: Ref<Error | null> } => {
  const data = ref<T | null>(null);
  const loading = ref(true);
  const error = ref<Error | null>(null);
  const { setSSRData, getSSRData, isSSR } = useSSRDataStore();
  
  const cacheKey = `fetch_${url}`;
  
  const fetchData = async () => {
    try {
      loading.value = true;
      error.value = null;
      
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const result = await response.json();
      data.value = result;
      
      if (isSSR) {
        setSSRData(cacheKey, result);
      }
    } catch (err) {
      error.value = err instanceof Error ? err : new Error(String(err));
      if (isSSR) {
        setSSRData(cacheKey, null);
      }
    } finally {
      loading.value = false;
    }
  };
  
  if (isSSR) {
    // Fetch on server
    fetchData();
  } else {
    // Try to get from SSR cache first
    const cachedData = getSSRData(cacheKey);
    if (cachedData !== null) {
      data.value = cachedData;
      loading.value = false;
    } else {
      // Fetch on client if not in cache
      fetchData();
    }
  }
  
  return { data, loading, error };
};

Types

interface SSRContext extends Record<string, any> {
  // HTML document metadata
  title?: string;
  meta?: Array<{ name?: string; property?: string; content: string }>;
  links?: Array<Record<string, string>>;
  scripts?: Array<{ src?: string; innerHTML?: string; [key: string]: any }>;
  
  // Resource optimization
  preloadLinks?: Array<{ rel: 'preload'; href: string; as: string; crossorigin?: string }>;
  prefetchLinks?: Array<{ rel: 'prefetch'; href: string }>;
  stylesheets?: Array<{ href: string; media?: string }>;
  criticalCSS?: string[];
  
  // Application state
  state?: Record<string, any>;
  
  // Request context (server-only)
  url?: string;
  request?: any;
  response?: any;
}

// Global SSR state interface
declare global {
  interface Window {
    __SSR_STATE__?: Record<string, any>;
  }
}

type SSRContextKey = InjectionKey<SSRContext>;

Server-Side Integration

Express.js Integration

// Server-side rendering with Express
app.get('*', async (req, res) => {
  const ssrContext: SSRContext = {
    url: req.url,
    request: req,
    response: res,
    state: {}
  };
  
  try {
    const html = await renderToString(app, ssrContext);
    
    // Build HTML with SSR context data
    const finalHtml = `
      <!DOCTYPE html>
      <html>
        <head>
          <title>${ssrContext.title || 'My App'}</title>
          ${ssrContext.meta?.map(meta => 
            `<meta ${meta.name ? `name="${meta.name}"` : ''} 
                   ${meta.property ? `property="${meta.property}"` : ''} 
                   content="${meta.content}">`
          ).join('\n') || ''}
          
          ${ssrContext.preloadLinks?.map(link =>
            `<link rel="${link.rel}" href="${link.href}" as="${link.as}" 
                   ${link.crossorigin ? `crossorigin="${link.crossorigin}"` : ''}>`
          ).join('\n') || ''}
          
          ${ssrContext.criticalCSS?.map(css => 
            `<style>${css}</style>`
          ).join('\n') || ''}
        </head>
        <body>
          <div id="app">${html}</div>
          <script>
            window.__SSR_STATE__ = ${JSON.stringify(ssrContext.state)};
          </script>
          <script src="/dist/client.js"></script>
        </body>
      </html>
    `;
    
    res.send(finalHtml);
  } catch (error) {
    console.error('SSR Error:', error);
    res.status(500).send('Server Error');
  }
});

Best Practices

  1. Hydration Mismatch Prevention: Ensure server and client render identical content
  2. Performance Optimization: Use resource preloading and critical CSS extraction
  3. Error Handling: Gracefully handle SSR failures with client-side fallbacks
  4. State Management: Properly serialize and transfer state between server and client
  5. SEO Optimization: Use SSR context for meta tags and structured data

Install with Tessl CLI

npx tessl i tessl/npm-vue--runtime-core

docs

asset-resolution.md

builtin-components.md

components.md

composition-helpers.md

dependency-injection.md

error-handling.md

hydration.md

index.md

internal-render-helpers.md

lifecycle.md

reactivity.md

scheduler-timing.md

ssr-context.md

vdom-rendering.md

watch-effects.md

tile.json