Articles on: Integrations

How do I connect my Framer blog?

Open Menu -> Connect my Blog -> Framer
Copy the provided API key and click the Connect button. If you need to retrieve your API key later, you can find it in Menu -> Blog Sync Status.
Navigate to your Framer dashboard and install the SEObot plugin. You can also find the SEObot plugin in the Framer marketplace.
Framer will prompt you to create a collection. Please give your collection a name.

Create a collection for SEObot articles

You will then be asked to enter the SEObot API key. Input the key provided by SEObot in step 2 above. Click the Save & Sync button.

Enter SEObot API key

Create Index and Details pages using the SEObot collection.

Create SEObot collection pages

Design the Details and Index pages.

SEObot page layers

On the Details page, select the Content field layer and make sure that all required styles are applied: Paragraph, Headings, Link, Blockquote, Code, Spacing, Code Block, YouTube, Image.

Setup the Details page styles

To support table rendering, you need to create a code override. Navigate to Assets and create a new code file in the Code section.

Create Code Override File

Copy and paste the code below, save the file. You can adjust the code, colors for dark and light themes, spacing, etc.

"use client"

import * as React from "react"
import type { ComponentType } from "react"
import { createStore } from "https://framer.com/m/framer/store.js@^1.0.0"

export const tableBorderRadius = 8
export const cellPadding = 8

export const lightBgColor = "#fff"
export const lightTextColor = "#000"
export const lightBorderColor = "#ccc"
export const lightHeaderColor = "#f0f0f0"
export const lightEvenRowBg = "#fafafa"

export const darkBgColor = "#222"
export const darkTextColor = "#fff"
export const darkBorderColor = "#555"
export const darkHeaderColor = "#333"
export const darkEvenRowBg = "#2a2a2a"

function createTableOverride({ colorScheme }) {
    const useStore = createStore({})

    return (Component) =>
        function TableOverride(props) {
            const [store] = useStore()

            const extractAndFormatTable = React.useCallback(() => {
                if (!window || !document) return
                const contentDivs = document.querySelectorAll(
                    '[data-framer-name="Content"][data-framer-component-type="RichTextContainer"]'
                )
                contentDivs.forEach((contentDiv) => {
                    const codeBlocks = contentDiv.querySelectorAll(
                        ".framer-text.framer-text-module"
                    )
                    codeBlocks.forEach((codeBlock) => {
                        if (!(codeBlock instanceof HTMLElement)) return
                        // Skip code blocks that have already been processed
                        if (codeBlock.dataset.processed) return
                        // Mark the code block as processed
                        codeBlock.dataset.processed = "true"
                        const finalScheme =
                            colorScheme === "auto" ? "light" : colorScheme

                        const outerContainer = document.createElement("div")
                        outerContainer.style.cssText = `
                  border: 1px solid ${
                      finalScheme === "dark"
                          ? darkBorderColor
                          : lightBorderColor
                  };
                  border-radius: ${tableBorderRadius}px;
                  overflow: hidden;
                  width: 100%;
                  height: 100%;
                `
                        const innerContainer = document.createElement("div")
                        innerContainer.style.cssText = `
                  overflow: auto;
                  width: 100%;
                  height: 100%;
                `
                        const codeContent = codeBlock.textContent || ""
                        const lines = codeContent
                            .split(/\r?\n/)
                            .filter((l) => l.trim() !== "")
                        if (!lines.length) return

                        const colCount = lines[0]
                            .split("|")
                            .filter((c) => c.trim()).length
                        const table = document.createElement("table")
                        table.style.cssText = `
                  border-collapse: collapse;
                  overflow: hidden;
                  width: 100%;
                  height: 100%;
                `

                        const tableHtml = lines
                            .map((row, i) => {
                                if (i === 1) return ""
                                const cells = row
                                    .split("|")
                                    .map((c) => c.trim())
                                    .filter(Boolean)
                                const isHeader = i === 0
                                const rowBg =
                                    i > 1 && i % 2 === 0
                                        ? finalScheme === "dark"
                                            ? darkEvenRowBg
                                            : lightEvenRowBg
                                        : finalScheme === "dark"
                                          ? darkBgColor
                                          : lightBgColor
                                const bgColor = isHeader
                                    ? finalScheme === "dark"
                                        ? darkHeaderColor
                                        : lightHeaderColor
                                    : rowBg
                                const tag = isHeader ? "th" : "td"
                                return `<tr style="border-color:${
                                    finalScheme === "dark"
                                        ? darkBorderColor
                                        : lightBorderColor
                                };">${cells
                                    .map(
                                        (cell) => `
                          <${tag} style="
                            width:${100 / colCount}%;
                            background:${bgColor};
                            color:${finalScheme === "dark" ? darkTextColor : lightTextColor};
                            padding:${cellPadding}px;
                            border-color:${
                                finalScheme === "dark"
                                    ? darkBorderColor
                                    : lightBorderColor
                            };
                          ">${cell}</${tag}>
                        `
                                    )
                                    .join("")}</tr>`
                            })
                            .filter(Boolean)
                            .join("")

                        table.innerHTML = tableHtml
                        innerContainer.appendChild(table)
                        outerContainer.appendChild(innerContainer)
                        codeBlock.innerHTML = ""
                        codeBlock.appendChild(outerContainer)
                    })
                })
            }, [store, colorScheme])

            React.useEffect(() => {
                if (!window || !document) return
                extractAndFormatTable()
                const observer = new MutationObserver(extractAndFormatTable)
                const contentDivs = document.querySelectorAll(
                    '[data-framer-name="Content"][data-framer-component-type="RichTextContainer"]'
                )
                contentDivs.forEach((c) =>
                    observer.observe(c, { childList: true, subtree: true })
                )
                const style = document.createElement("style")
                style.textContent = `
              [data-framer-name="Content"][data-framer-component-type="RichTextContainer"] {
                white-space: normal !important;
                opacity: 1 !important;
              }
            `
                document.head.appendChild(style)
                return () => {
                    observer.disconnect()
                    document.head.removeChild(style)
                }
            }, [extractAndFormatTable])

            return <Component {...props} />
        }
}

export function ThemeLight(Component): ComponentType {
    return createTableOverride({ colorScheme: "light" })(Component)
}

export function ThemeDark(Component): ComponentType {
    return createTableOverride({ colorScheme: "dark" })(Component)
}

export function ThemeAuto(Component): ComponentType {
    return createTableOverride({ colorScheme: "auto" })(Component)
}


Navigate to the Details page and select the Content field layer again. Then, choose the created Code Override in the Code Overrides section.

SEObot Code Override

Complete your pages design, setup page routes and publish changes.

Can I use the /blog/ route?



If you already have a blog collection that operates under the /blog/ route, you should choose a different option for SEObot blogs.

This route limitation is imposed by Framer, which does not allow plugins to manage third-party CMS collections—only its own collections. Additionally, you cannot use two CMS collections on the same route. However, you can create a separate route, such as /insights/ or /resources/. This is standard practice and helps organize different types of content more effectively.

Common examples:
/blog/ (main blog)
/insights/ (thought leadership)
/news/ (company updates)
/resources/ (guides, tutorials)
/case-studies/
etc.

Updated on: 16/01/2025

Was this article helpful?

Share your feedback

Cancel

Thank you!