Repository: ByteSliceHQ/deepdish
Pull request #208: feat(cms): export created components and configured middleware
Author: login: electric-gizzard
Created: 2025-03-23T20:47:34Z | Merged: 2025-03-23T22:57:26Z

Resolves DEEP-248, Contributes to DEEP-263


Diff:
diff --git a/.changeset/few-buckets-help.md b/.changeset/few-buckets-help.md
new file mode 100644
index 00000000..9273ee47
--- /dev/null
+++ b/.changeset/few-buckets-help.md
@@ -0,0 +1,5 @@
+---
+"@deepdish/cms": minor
+---
+
+Modified middleware to partially apply config.
diff --git a/.changeset/fuzzy-women-smell.md b/.changeset/fuzzy-women-smell.md
new file mode 100644
index 00000000..a063cafb
--- /dev/null
+++ b/.changeset/fuzzy-women-smell.md
@@ -0,0 +1,5 @@
+---
+"@deepdish/cms": minor
+---
+
+Exported created components and configured middleware from `deepdish` function.
diff --git a/.changeset/short-singers-film.md b/.changeset/short-singers-film.md
new file mode 100644
index 00000000..8de4cef0
--- /dev/null
+++ b/.changeset/short-singers-film.md
@@ -0,0 +1,5 @@
+---
+"@deepdish/cms": minor
+---
+
+Modified base url helper to apply defaults when no argument is provided.
diff --git a/README.md b/README.md
index 1f8b6eab..905b3828 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
 # DeepDish
 
-Welcome to DeepDish. DeepDish allows you to build Next.js apps without integrating a CMS.
-DeepDish is built on top of React server components and specifically designed for Next.js.
+DeepDish enables developers to build web applications without integrating a CMS, allowing content managers to make edits directly on each page.
 
-Created by the [ByteSlice](https://byteslice.co) team for devs and marketing teams.
+Specifically designed for [Next.js](https://nextjs.org/), DeepDish leverages the capabilities of [React Server Components](https://react.dev/reference/rsc/server-components).
+
+🍕 Built by the team at [ByteSlice](https://byteslice.co).
 
 ## Installation
 
@@ -21,14 +22,13 @@ bun add @deepdish/cms
 
 ## Creating an account
 
-We are actively building out the DeepDish platform. To get started, join the [DeepDish waitlist](https://www.deepdish.app).
-We are sending out invites to our waitlist every Friday to gain access to the platform.
+We are actively building out the DeepDish platform. To request access, please join the [waitlist](https://www.deepdish.app).
 
 ## Getting Started
 
-Follow the guide below to get started with DeepDish.
+Follow the guide below to get up-and-running with DeepDish.
 
-### Step 1: Setup your environment
+### Step 1: Set up your environment
 
 Set the following environment variables in your local `.env.local` file:
 
@@ -46,97 +46,91 @@ BASE_URL=http://localhost:3000
 
 #### Vercel helpers
 
-If you're using Vercel, you can omit the `BASE_URL` environment variable and use the following helpers to get the base URL based on the Vercel environment.
+If you're using Vercel, you can omit the `BASE_URL` environment variable and use the `getBaseUrl` helper instead.
 
 ```ts
-import { getBaseUrl } from "@deepdish/cms/vercel";
-const baseUrl = getBaseUrl();
+import { getBaseUrl } from "@deepdish/cms/vercel"
+
+const baseUrl = getBaseUrl()
 ```
 
-The `getBaseUrl` function will return the base URL based on the Vercel environment, or your local development endpoint if you're running the app locally.
-This is handy when using the Vercel preview feature.
+That helper function will return the base URL of the Vercel environment (or your local development endpoint if you're running the app locally). This is particularly useful when using the Vercel preview feature.
 
-### Step 2: Add a deepdish.ts file
+### Step 2: Configure your project
 
-Create a `deepdish.ts` file in the `app` directory of your Next.js project.
+Create a `deepdish.ts` file in the `app` directory of your Next.js project. Import the `deepdish` function from `@deepdish/cms` and initialize it with your configuration.
 
-`app/deepdish.ts`:
+> [!Note]
+> The `deepdish` function creates components and middleware based on your configuration. Ensure you export them so they can be used within your application.
 
 ```ts
-import type { DeepDishConfig } from '@deepdish/cms';
-
-// If you're using Vercel, use the following helpers to get the base URL.
-// import { getBaseUrl } from "@deepdish/cms/vercel";
-// const baseUrl = getBaseUrl();
-const baseUrl = process.env.BASE_URL;
+import { deepdish } from '@deepdish/cms'
 
 // Draft mode is dependent upon your environment.
 // Set to `true` to enable the DeepDish Workbench, or `false` to disable it.
 // We recommend using an environment variable for this.
-const draft = process.env.DEEPDISH_MODE === "draft";
+const draft = process.env.DEEPDISH_MODE === "draft"
 
-export const config: DeepDishConfig = {
+export const { components, middleware } = await deepdish({
   draft,
-  baseUrl,
+  baseUrl: process.env.BASE_URL, // or getBaseUrl() if using Vercel
   secretKey: process.env.DEEPDISH_SECRET_KEY,
   projectAlias: process.env.DEEPDISH_PROJECT_ALIAS,
-};
+})
 ```
 
-### Step 3: Configure your project
+### Step 3: Add the provider
 
-In your `app/layout.tsx` file, import the `deepdish` function from `@deepdish/cms` and initialize it with your `config` object.
-Additionally, import the `DeepDishProvider` component from `@deepdish/cms` and wrap your app with it.
+Import the `DeepDishProvider` component from `@deepdish/cms` and wrap the subtree where DeepDish components will be rendered.
 
 ```tsx
-import { deepdish, DeepDishProvider } from "@deepdish/cms";
-import { config } from "@/deepdish";
+import { DeepDishProvider } from "@deepdish/cms"
 
-await deepdish(config);
-
-export default function RootLayout({
-  children,
-}: Readonly<{
-  children: React.ReactNode;
-}>) {
+export default function RootLayout(props: { children: React.ReactNode }) {
   return (
     <html>
       <body>
         <DeepDishProvider>
-          {children}
+          {props.children}
         </DeepDishProvider>
       </body>
     </html>
-  );
+  )
 }
 ```
 
-### Step 4: Add the DeepDish Middleware
+### Step 4: Add the middleware
 
-In your `middleware.ts` file, import the `deepdishMiddleware` function from `@deepdish/cms` and initialize it with your `config` object.
+In your `middleware.ts` file—where [Next.js middleware](https://nextjs.org/docs/app/api-reference/file-conventions/middleware) is defined—add the configured DeepDish middleware.
 
 ```ts
-import { deepdishMiddleware } from "@deepdish/cms";
-import { config } from "@/deepdish";
+import type { NextResponse } from 'next/server'
+import * as deepdish from "@/deepdish"
 
-export default deepdishMiddleware(config);
+export default function (request: NextRequest) {
+  return deepdish.middleware(request)
+}
 ```
 
 ### Step 5: Add a DeepDish Component
 
-To add a DeepDish component to your page, import one and pass it a `deepdish` prop. That object requires a `key` to uniquely identify the component.
+To add a DeepDish component to your page, import one that has been created based on your configuration. Pass it a `deepdish` prop with a `key` unique to the data you want to render. The `render` prop will be given a strongly-typed `value` that you can use to render whatever you'd like!
 
 ```tsx
-import { Header1 } from "@/deepdish";
+import * as deepdish from "@/deepdish"
+
+const { Text } = deepdish.components
 
 function Home() {
   return (
     <div>
-      <Header1 deepdish={{ key: "title" }}>
-        Header Fallback
-      </Header1>
+      <Text
+        deepdish={{ key: 'headline' }}
+        fallback="Grab a slice!"
+        render={async (value) => <p className="text-xl font-bold">{value}</p>}
+      />
     </div>
-  );
+  )
 }
 ```
 
diff --git a/packages/cms/src/cms.ts b/packages/cms/src/cms.ts
index d1092d3b..d90a8ace 100644
--- a/packages/cms/src/cms.ts
+++ b/packages/cms/src/cms.ts
@@ -1,7 +1,8 @@
 import { createTypographyResolver } from '@deepdish-cloud/resolvers/typography'
 import { schema } from '@deepdish/core/schema'
+import { createComponents } from '@deepdish/ui/components'
 import { configure } from '@deepdish/ui/config'
-import { deepdishMiddleware } from './middleware'
+import { middleware } from './middleware'
 import { ProviderContainer as DeepDishProvider } from './provider/container'
 
 export type DeepDishConfig = {
@@ -22,17 +23,19 @@ export const deepdish = async (config: DeepDishConfig) => {
     config.projectAlias,
   )
 
-  return await configure({
-    contracts: {
-      typography: {
-        resolver: {
-          // TODO: Remove this once the cloud resolver supports keys.
-          keys: () => Promise.resolve({ success: false, data: [] }),
-          ...cloudTypographyResolver,
-        },
-        schema: schema((v) => v.string()),
+  const contracts = {
+    text: {
+      resolver: {
+        // TODO: Remove this once the cloud resolver supports keys.
+        keys: () => Promise.resolve({ success: false, data: [] }),
+        ...cloudTypographyResolver,
       },
+      schema: schema((v) => v.string()),
     },
+  }
+
+  await configure({
+    contracts,
     logging: {
       enabled: config.loggingEnabled ?? false,
     },
@@ -41,6 +44,16 @@ export const deepdish = async (config: DeepDishConfig) => {
       draft: config.draft,
     },
   })
+
+  const components = createComponents(contracts)
+
+  return {
+    // TODO: map contract names to component names
+    components: {
+      Text: components.text,
+    },
+    middleware: middleware(config),
+  }
 }
 
-export { DeepDishProvider, deepdishMiddleware }
+export { DeepDishProvider }
diff --git a/packages/cms/src/middleware.ts b/packages/cms/src/middleware.ts
index 5cb8f11a..02868a95 100644
--- a/packages/cms/src/middleware.ts
+++ b/packages/cms/src/middleware.ts
@@ -1,4 +1,4 @@
-import { deepdishMiddleware as middleware } from '@deepdish/nextjs'
+import { deepdishMiddleware } from '@deepdish/nextjs'
 import { type NextRequest, NextResponse } from 'next/server'
 import type { DeepDishConfig } from './cms'
 
@@ -21,18 +21,17 @@ async function verify(_request: NextRequest) {
   return true
 }
 
-export const deepdishMiddleware = (
-  config: DeepDishConfig,
-  request: NextRequest,
-  response?: NextResponse,
-) =>
-  middleware(
-    {
-      draft: config.draft,
-      verify,
-      signIn,
-      signOut,
-    },
-    request,
-    response,
-  )
+export function middleware(config: DeepDishConfig) {
+  return (request: NextRequest, response?: NextResponse) => {
+    return deepdishMiddleware(
+      {
+        draft: config.draft,
+        verify,
+        signIn,
+        signOut,
+      },
+      request,
+      response,
+    )
+  }
+}
diff --git a/packages/cms/src/vercel/index.ts b/packages/cms/src/vercel/index.ts
index 4f5b468b..86e3c117 100644
--- a/packages/cms/src/vercel/index.ts
+++ b/packages/cms/src/vercel/index.ts
@@ -1,12 +1,14 @@
-export function getBaseUrl({
-  productionBaseProtocol = 'https',
-  previewBaseProtocol = 'https',
-  localhost = 'http://localhost:3000',
-}: {
+type BaseUrlConfig = {
   productionBaseProtocol?: 'https' | 'http'
   previewBaseProtocol?: 'https' | 'http'
   localhost?: string
-}) {
+}
+
+export function getBaseUrl(config?: BaseUrlConfig) {
+  const productionBaseProtocol = config?.productionBaseProtocol ?? 'https'
+  const previewBaseProtocol = config?.previewBaseProtocol ?? 'https'
+  const localhost = config?.localhost ?? 'http://localhost:3000'
+
   // biome-ignore lint/nursery/noProcessEnv: Required for Vercel integration
   switch (process.env.VERCEL_ENV) {
     case 'production':


Reviews:
user: login: electric-gizzard
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T20:51:07Z
user: login: electric-gizzard
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T20:59:17Z
user: login: dylnslck
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T21:19:21Z
user: login: dylnslck
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T21:24:35Z
user: login: electric-gizzard
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T22:45:39Z
user: login: electric-gizzard
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T22:46:02Z
user: login: dylnslck
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T22:51:41Z
user: login: dylnslck
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T22:52:48Z

Review comments:
diff_hunk: @@ -21,14 +22,13 @@ bun add @deepdish/cms
 
 ## Creating an account
 
-We are actively building out the DeepDish platform. To get started, join the [DeepDish waitlist](https://www.deepdish.app).
-We are sending out invites to our waitlist every Friday to gain access to the platform.
path: README.md
user: login: electric-gizzard
body: This "invites" line has been removed since it is not currently accurate.
created_at: 2025-03-23T20:51:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,6 +44,16 @@ export const deepdish = async (config: DeepDishConfig) => {
       draft: config.draft,
     },
   })
+
+  const components = createComponents(contracts)
+
+  return {
+    // TODO: map contract names to component names
+    components: {
+      Text: components.text,
+    },
+    middleware: deepdishMiddleware.bind(null, config),
+  }
path: packages/cms/src/cms.ts
user: login: electric-gizzard
body: Please leave your thoughts about this modified interface.

As noted in the README, developers can call `deepdish` once with their configuration and not need to export `config` with an imported `DeepDishConfig` annotation.

This does modify how `components` in particular are imported, but this interface is more extensible and simplifies the configuration process.
created_at: 2025-03-23T20:59:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,6 +44,16 @@ export const deepdish = async (config: DeepDishConfig) => {
       draft: config.draft,
     },
   })
+
+  const components = createComponents(contracts)
+
+  return {
+    // TODO: map contract names to component names
+    components: {
+      Text: components.text,
+    },
+    middleware: deepdishMiddleware.bind(null, config),
+  }
path: packages/cms/src/cms.ts
user: login: dylnslck
body: @electric-gizzard I like the simplicity, but how would this interface work with apps that use more than a single middleware? Could I use what's exported here as a link in a middleware chain?
created_at: 2025-03-23T21:19:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,97 +46,88 @@ BASE_URL=http://localhost:3000
 
 #### Vercel helpers
 
-If you're using Vercel, you can omit the `BASE_URL` environment variable and use the following helpers to get the base URL based on the Vercel environment.
+If you're using Vercel, you can omit the `BASE_URL` environment variable and use the `getBaseUrl` helper instead.
 
 ```ts
-import { getBaseUrl } from "@deepdish/cms/vercel";
-const baseUrl = getBaseUrl();
+import { getBaseUrl } from "@deepdish/cms/vercel"
+
+const baseUrl = getBaseUrl()
path: README.md
user: login: dylnslck
body: I believe that `getBaseUrl` requires an argument. If you want to use defaults, you need to pass in an empty object. That's silly—we should refactor `getBaseUrl` to support accepting no arguments.
created_at: 2025-03-23T21:24:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,97 +46,88 @@ BASE_URL=http://localhost:3000
 
 #### Vercel helpers
 
-If you're using Vercel, you can omit the `BASE_URL` environment variable and use the following helpers to get the base URL based on the Vercel environment.
+If you're using Vercel, you can omit the `BASE_URL` environment variable and use the `getBaseUrl` helper instead.
 
 ```ts
-import { getBaseUrl } from "@deepdish/cms/vercel";
-const baseUrl = getBaseUrl();
+import { getBaseUrl } from "@deepdish/cms/vercel"
+
+const baseUrl = getBaseUrl()
path: README.md
user: login: electric-gizzard
body: That is a little silly. I've modified the `getBaseUrl` implementation to permit no arguments to apply the defaults.


created_at: 2025-03-23T22:45:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,6 +44,16 @@ export const deepdish = async (config: DeepDishConfig) => {
       draft: config.draft,
     },
   })
+
+  const components = createComponents(contracts)
+
+  return {
+    // TODO: map contract names to component names
+    components: {
+      Text: components.text,
+    },
+    middleware: deepdishMiddleware.bind(null, config),
+  }
path: packages/cms/src/cms.ts
user: login: electric-gizzard
body: I'd prefer we avoid being overly prescriptive about middleware chaining, but I've modified the `cms` middleware to partially apply the `config` and the README to more clearly document how the single Next.js middleware function would be written.
created_at: 2025-03-23T22:46:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -41,6 +44,16 @@ export const deepdish = async (config: DeepDishConfig) => {
       draft: config.draft,
     },
   })
+
+  const components = createComponents(contracts)
+
+  return {
+    // TODO: map contract names to component names
+    components: {
+      Text: components.text,
+    },
+    middleware: deepdishMiddleware.bind(null, config),
+  }
path: packages/cms/src/cms.ts
user: login: dylnslck
body: Nice. Without this, DeepDish would be a non-starter for apps that are already using middleware (e.g. Clerk or whatever).
created_at: 2025-03-23T22:51:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-23T20:47:37Z
body: ###  🦋  Changeset detected

Latest commit: 2ef2aa5608691339b23750f738631a1f0b795364

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 3 packages</summary>

  | Name                | Type  |
| ------------------- | ----- |
| @deepdish/cms       | Minor |
| @deepdish/demo      | Patch |
| @deepdish/marketing | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/ByteSliceHQ/deepdish/new/cms-components?filename=.changeset/dirty-islands-talk.md&value=---%0A%22%40deepdish%2Fcms%22%3A%20patch%0A---%0A%0Afeat(cms)%3A%20export%20created%20components%20and%20configured%20middleware%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-03-23T20:47:38Z
body: [vc]: #+v6fPKBRK12VH3MbhnH+s+qXmDFZ6ZAT8E8+Up/dL4Y=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJkZWVwZGlzaC1tYXJrZXRpbmciLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZGVlcGRpc2gtbWFya2V0aW5nLWdpdC1jbXMtY29tcG9uZW50cy1ieXRlLXNsaWNlLTY1M2E5MjVkLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2J5dGUtc2xpY2UtNjUzYTkyNWQvZGVlcGRpc2gtbWFya2V0aW5nL0M1YWR2cTVYQmdvQW90dXRtQ05MOEtyR2VpTVAiLCJwcmV2aWV3VXJsIjoiZGVlcGRpc2gtbWFya2V0aW5nLWdpdC1jbXMtY29tcG9uZW50cy1ieXRlLXNsaWNlLTY1M2E5MjVkLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9tYXJrZXRpbmcifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **deepdish-marketing** | ✅ Ready ([Inspect](https://vercel.com/byte-slice-653a925d/deepdish-marketing/C5advq5XBgoAotutmCNL8KrGeiMP)) | [Visit Preview](https://deepdish-marketing-git-cms-components-byte-slice-653a925d.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/deepdish-marketing-git-cms-components-byte-slice-653a925d.vercel.app?via=pr-comment-feedback-link) | Mar 23, 2025 10:46pm |


author_association: NONE