@picked/client 0.0.64-next.3 → 0.0.64-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/{api-DF8BSFOG.js → api-ClqYYoYS.js} +1 -1
  2. package/dist/{api-Dktr48v4.cjs → api-DYgsf40d.cjs} +1 -1
  3. package/dist/cjs/form/index.js +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/create-experience/index.js +1 -1
  6. package/dist/cjs/plugins/dom-observer/index.js +1 -1
  7. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  8. package/dist/cjs/plugins/experience-browser-storage/index.js +1 -1
  9. package/dist/cjs/plugins/history-browser-storage/index.js +1 -1
  10. package/dist/cjs/plugins/responses-browser-storage/index.js +1 -1
  11. package/dist/cjs/plugins/responses-indexeddb/index.js +1 -1
  12. package/dist/cjs/plugins/single-page-app/index.js +1 -1
  13. package/dist/cjs/plugins/web-components/index.js +1 -1
  14. package/dist/cjs/plugins/window-object/index.js +1 -1
  15. package/dist/cjs/server/index.js +1 -1
  16. package/dist/docs/api/index.md +4 -0
  17. package/dist/docs/content/asset-library.mdx +50 -0
  18. package/dist/docs/content/content-entries.mdx +30 -0
  19. package/dist/docs/content/layouts.mdx +70 -0
  20. package/dist/docs/content/pages.mdx +67 -0
  21. package/dist/docs/content/sections.mdx +33 -0
  22. package/dist/docs/delivery-api/experience-content.md +85 -0
  23. package/dist/docs/delivery-api/index.md +46 -0
  24. package/dist/docs/delivery-api/preview-caching-crawling.md +59 -0
  25. package/dist/docs/delivery-api/project-config.md +56 -0
  26. package/dist/docs/experiences.mdx +3 -1
  27. package/dist/docs/fields.mdx +2 -0
  28. package/dist/docs/guides/build-a-personalized-page.mdx +143 -0
  29. package/dist/docs/sdk/experiences.md +2 -0
  30. package/dist/docs/sdk/index.md +11 -1
  31. package/dist/docs/sdk/ssr/index.md +62 -0
  32. package/dist/docs/sdk/ssr/rendering-and-hydration.md +141 -0
  33. package/dist/docs/sdk/ssr/serialize.md +86 -0
  34. package/dist/docs/sdk/ssr/slots-and-placements.md +35 -0
  35. package/dist/docs/steps.mdx +2 -0
  36. package/dist/{dom-DjfSrkpp.js → dom-BwmTHeT7.js} +1 -1
  37. package/dist/{dom-sqnnO-CT.cjs → dom-CDrHc_j9.cjs} +1 -1
  38. package/dist/esm/form/index.js +1 -1
  39. package/dist/esm/index.js +10 -10
  40. package/dist/esm/plugins/create-experience/index.js +1 -1
  41. package/dist/esm/plugins/dom-observer/index.js +3 -3
  42. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  43. package/dist/esm/plugins/experience-browser-storage/index.js +3 -3
  44. package/dist/esm/plugins/history-browser-storage/index.js +2 -2
  45. package/dist/esm/plugins/responses-browser-storage/index.js +2 -2
  46. package/dist/esm/plugins/responses-indexeddb/index.js +1 -1
  47. package/dist/esm/plugins/single-page-app/index.js +2 -2
  48. package/dist/esm/plugins/web-components/index.js +1 -1
  49. package/dist/esm/plugins/window-object/index.js +1 -1
  50. package/dist/esm/server/index.js +2 -2
  51. package/dist/{experience-instance-BT_H2Zln.cjs → experience-instance-CGzLsO7F.cjs} +1 -1
  52. package/dist/{experience-instance-v635I4oP.js → experience-instance-RJiegFtc.js} +3 -3
  53. package/dist/id-Js-62re-.cjs +1 -0
  54. package/dist/logger-CSTvAyyB.cjs +1 -0
  55. package/dist/{object-ThO1DEyl.cjs → object-CIkt7Fkr.cjs} +1 -1
  56. package/dist/{plugin-BQiuKtPl.js → plugin-DlwmZl2C.js} +2 -2
  57. package/dist/{plugin-DbiThXW5.cjs → plugin-vNgJacw6.cjs} +1 -1
  58. package/dist/{preview-bridge-DIdDvNXQ.cjs → preview-bridge-CpX1BkSA.cjs} +1 -1
  59. package/dist/{preview-bridge-CGyx7dEx.js → preview-bridge-D76l-dPy.js} +2 -2
  60. package/dist/rules-logic-CGZIYgAI.cjs +1 -0
  61. package/dist/{rules-logic-D_CVBuv7.js → rules-logic-HesIUCu6.js} +1 -1
  62. package/dist/{storage-BXSlLTRa.cjs → storage-DUw9UJ2h.cjs} +1 -1
  63. package/dist/{supports-34OzZK5j.cjs → supports-DBzjGaWD.cjs} +1 -1
  64. package/package.json +1 -1
  65. package/dist/id-Cg7fVl1O.cjs +0 -1
  66. package/dist/logger-fbvQC40v.cjs +0 -1
  67. package/dist/rules-logic-H6P8xwHF.cjs +0 -1
  68. /package/dist/{id-n2yrHvNo.js → id-Bo4RBeOs.js} +0 -0
  69. /package/dist/{logger-6z_gb4rA.js → logger-DMN6TxJJ.js} +0 -0
  70. /package/dist/{object-DvUDOytC.js → object-BUDGxEqy.js} +0 -0
  71. /package/dist/{storage-hOHCCXvP.js → storage-1MW3-_As.js} +0 -0
  72. /package/dist/{supports-BgjwyijI.js → supports-CO9bJGNl.js} +0 -0
@@ -0,0 +1,67 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ import { Screenshot } from '../../../components/Screenshot'
6
+
7
+ # Pages
8
+
9
+ A Page is a standalone, server-rendered experience backed by CMS content, built for SEO-critical landing pages.
10
+
11
+ <Screenshot
12
+ route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/experiences"
13
+ alt="The experiences list, where Pages and Flows are created on the Picked dashboard"
14
+ />
15
+
16
+ ## Pages vs flows
17
+
18
+ Picked has two kinds of experience:
19
+
20
+ | Experience | What it is | Use it for |
21
+ | --- | --- | --- |
22
+ | **Page** | A standalone experience rendered on your server, backed by a Content Entry. | SEO-critical landing pages, campaign pages, content the crawler must see. |
23
+ | **Flow** | A multi-step, interactive experience rendered in the browser. | Quizzes, surveys, product finders, multi-step onboarding. |
24
+
25
+ Reach for a **Page** when the content must appear in the server HTML for search engines and social unfurls. Reach for a **Flow** when the value is in the interaction, and client-side rendering is fine. See [Experiences](/experiences) for the wider model.
26
+
27
+ ## How a page fits together
28
+
29
+ Four pieces back every Page:
30
+
31
+ 1. A **Page Layout** defines the shape: the fields a page can hold. See [Page & Step Layouts](/content/layouts).
32
+ 2. A **Content Entry** fills that shape with real data. See [Content Entries](/content/content-entries).
33
+ 3. A **Page** binds one entry so the SDK can render it.
34
+ 4. The **SDK** fetches the entry and renders it on your server. See [Server-Side Rendering](/sdk/ssr).
35
+
36
+ A layout defines the shape, a content entry fills it with data, a Page binds an entry so the SDK can render it. Step Layouts do the same for individual Steps inside a Flow.
37
+
38
+ ```
39
+ Author (dashboard) -> Publish -> Delivery API (config + content)
40
+ -> serialize() on server -> Provider seeds client -> ExperienceSlot renders -> hydrate
41
+ ```
42
+
43
+ ## Create a page
44
+
45
+ 1. Open your project and select a workspace.
46
+ 2. Click **Experiences** in the sidebar.
47
+ 3. Click **New Experience** and choose the **Page** type.
48
+ 4. Pick a **Page Layout**. The layout determines which fields the page holds.
49
+ 5. Click **Create**.
50
+
51
+ A Content Entry is created and linked to the page on its first save, so the page always has somewhere to store its data.
52
+
53
+ ## Edit page content
54
+
55
+ The Page editor has a **Properties** tab for the page's content and a **Settings** tab for its name, condition, and layout. Fill in the fields on the **Properties** tab. The inspector on the right edits the selected field, and the preview panel shows the page as it fills in.
56
+
57
+ ## Change or remove the layout
58
+
59
+ Use the change-layout control on the **Settings** tab to switch the page to a different Page Layout. Unlinking a layout detaches the Content Entry from the page. The entry is not deleted, but the page no longer has content to render until you bind a layout again.
60
+
61
+ ## Validate and publish
62
+
63
+ Invalid fields surface inline as per-field errors while you edit, so you can fix them before publishing. When the page is valid, publish the workspace to make it live. Publishing produces the version the Delivery API serves to anonymous readers.
64
+
65
+ ## Render a page
66
+
67
+ A published Page is rendered by the SDK on your server. See the [Build a Personalized Page](/guides/build-a-personalized-page) guide for the end-to-end walkthrough, and [Server-Side Rendering](/sdk/ssr) for the SDK reference.
@@ -0,0 +1,33 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ import { Screenshot } from '../../../components/Screenshot'
6
+
7
+ # Sections
8
+
9
+ Sections are reusable schema fragments you embed inside layouts, so common structures such as a hero or a feature list are defined once.
10
+
11
+ <Screenshot
12
+ route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/blocks"
13
+ alt="The sections list in the schema builder on the Picked dashboard"
14
+ />
15
+
16
+ ## What a section is
17
+
18
+ A Section is a reusable fragment of schema with its own **Key**. Where a [layout](/content/layouts) backs a whole Page or Step, a Section is a piece you compose into one or more layouts. Define the shape once, reuse it everywhere.
19
+
20
+ ## Create a section
21
+
22
+ 1. Open your workspace and click **Sections** in the sidebar.
23
+ 2. Click **New Section**.
24
+ 3. Enter a name and a **Key**. The Key is a unique, stable identifier used in SDK code and API references.
25
+ 4. Add fields and save.
26
+
27
+ ## Use a section in a layout
28
+
29
+ Add a **Section** field to a Page or Step Layout and point it at the Section you want to embed. The layout then carries the Section's fields wherever that field appears. A Content Entry built from the layout fills the Section's fields like any others.
30
+
31
+ ## Changing a section
32
+
33
+ Editing a Section does not automatically migrate the entries that already use it. After changing a Section, revalidate the affected [Content Entries](/content/content-entries) and resolve any per-field errors before publishing.
@@ -0,0 +1,85 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Experience Content Endpoint
6
+
7
+ Fetch the Content Entries that back a single experience's steps.
8
+
9
+ ## Request
10
+
11
+ ```
12
+ GET /api/v1/projects/:projectId/experiences/:id/content
13
+ ```
14
+
15
+ | Parameter | In | Description |
16
+ | --- | --- | --- |
17
+ | `projectId` | path | The project the experience belongs to. |
18
+ | `id` | path | The experience to fetch content for. |
19
+ | `preview_token` | query | Optional. Returns preview (unpublished) content when present. |
20
+
21
+ ## Response
22
+
23
+ ```json
24
+ {
25
+ "experienceId": "exp_123",
26
+ "versionId": "ver_456",
27
+ "content": [
28
+ {
29
+ "id": "entry_789",
30
+ "contentModelKey": "page-hero",
31
+ "data": { "title": "Spring sale", "body": "..." }
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ | Field | Type | Description |
38
+ | --- | --- | --- |
39
+ | `experienceId` | `string` | The experience the content belongs to. |
40
+ | `versionId` | `string \| null` | The published version id, or `null` on preview. |
41
+ | `content[].id` | `string` | The Content Entry id. |
42
+ | `content[].contentModelKey` | `string \| null` | The layout's Key, or `null` when it cannot be resolved. |
43
+ | `content[].data` | `object` | The entry data, matching the layout schema. |
44
+
45
+ ## HTML asset inlining
46
+
47
+ When the layout has an [HTML asset](/content/asset-library) field, its value arrives inlined. The reference is rewritten to `{ id, content }`, where `content` is the uploaded HTML as a string (or `null` when it cannot be resolved):
48
+
49
+ ```json
50
+ {
51
+ "id": "entry_789",
52
+ "contentModelKey": "page-hero",
53
+ "data": {
54
+ "markup": { "id": "asset_42", "content": "<section>...</section>" }
55
+ }
56
+ }
57
+ ```
58
+
59
+ The SDK renders the inlined HTML directly, with no extra storage fetch.
60
+
61
+ ## Status codes
62
+
63
+ | Code | Meaning |
64
+ | --- | --- |
65
+ | `200` | Success. |
66
+ | `401` | Invalid `preview_token`. |
67
+ | `404` | The experience has no published content. |
68
+
69
+ ## Example
70
+
71
+ ```bash
72
+ curl https://api.picked.app/api/v1/projects/proj_123/experiences/exp_123/content
73
+ ```
74
+
75
+ ```json
76
+ {
77
+ "experienceId": "exp_123",
78
+ "versionId": "ver_456",
79
+ "content": [
80
+ { "id": "entry_789", "contentModelKey": "page-hero", "data": { "title": "Spring sale" } }
81
+ ]
82
+ }
83
+ ```
84
+
85
+ See [Project Config](/delivery-api/project-config) for the endpoint that links steps to entries, [Asset Library](/content/asset-library) for HTML assets, and [Server Snapshot](/sdk/ssr/serialize) for how the SDK consumes this content.
@@ -0,0 +1,46 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Delivery API
6
+
7
+ The Delivery API is a public, read-only REST API that serves your published project config and page content. It is separate from the GraphQL management API.
8
+
9
+ ## Delivery API vs the management API
10
+
11
+ Picked has two HTTP APIs. Use the one that fits the job:
12
+
13
+ | | Delivery API | Management API |
14
+ | --- | --- | --- |
15
+ | Style | REST | GraphQL |
16
+ | Access | Public, anonymous | Authenticated |
17
+ | Operations | Read-only | Read and write |
18
+ | Use for | Serving published config and content to your app | Managing projects, experiences, and content |
19
+
20
+ The management API is documented under [API Reference](/api). This section covers the Delivery API.
21
+
22
+ ## Base URL and versioning
23
+
24
+ All Delivery API routes are versioned under `/api/v1`:
25
+
26
+ ```
27
+ https://api.picked.app/api/v1
28
+ ```
29
+
30
+ ## Published vs preview
31
+
32
+ Each endpoint serves two surfaces:
33
+
34
+ - **Published** content is anonymous. Anyone can read your published config and content.
35
+ - **Preview** content is gated by a `preview_token` query parameter, so you can render unpublished drafts before going live.
36
+
37
+ A workspace's preview token is available as the `previewToken` field on the workspace in the management API, and the dashboard preview action issues one for you.
38
+
39
+ ## Endpoints
40
+
41
+ | Endpoint | Description |
42
+ | --- | --- |
43
+ | [Project Config](/delivery-api/project-config) | `GET /api/v1/projects/:projectId` returns the published project config, including each step's content link. |
44
+ | [Experience Content](/delivery-api/experience-content) | `GET /api/v1/projects/:projectId/experiences/:id/content` returns the Content Entries that back an experience. |
45
+
46
+ For the operational details shared by both endpoints, see [Preview, Caching & Crawling](/delivery-api/preview-caching-crawling).
@@ -0,0 +1,59 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Preview, Caching & Crawling
6
+
7
+ The operational surface shared by both Delivery API endpoints: preview tokens, caching, CORS, and crawling.
8
+
9
+ ## Preview tokens
10
+
11
+ Pass `?preview_token=<token>` to read unpublished content. Both endpoints accept it, and both return `versionId: null` on the preview surface. An invalid token returns `401`.
12
+
13
+ ```bash
14
+ curl "https://api.picked.app/api/v1/projects/proj_123?preview_token=<token>"
15
+ ```
16
+
17
+ A workspace's preview token is available as the `previewToken` field on the workspace in the [management API](/api), and the dashboard preview action issues one for you. Use the same token for the project config and the experience content requests so both come from the same preview surface.
18
+
19
+ ## Caching
20
+
21
+ Caching depends on the surface and the requesting client. The SDK sends a `Picked-Client: sdk` request header:
22
+
23
+ | Request | `Cache-Control` |
24
+ | --- | --- |
25
+ | Published, `Picked-Client: sdk` | `public, s-maxage=60, stale-while-revalidate=86400` |
26
+ | Published, other clients | `private, no-store` |
27
+ | Preview | `no-store` |
28
+
29
+ Cacheable published responses also send `Vary: Picked-Client`, so a shared cache keys on the client header and never serves an SDK-cached body to a non-SDK request.
30
+
31
+ ## CORS
32
+
33
+ Both endpoints are cross-origin readable. An `OPTIONS` preflight returns `204` with the CORS headers:
34
+
35
+ | Header | Value |
36
+ | --- | --- |
37
+ | `Access-Control-Allow-Origin` | `*` |
38
+ | `Access-Control-Allow-Methods` | `GET, OPTIONS` |
39
+ | `Access-Control-Allow-Headers` | `Content-Type, Picked-Client, Picked-Version, traceparent, tracestate` |
40
+
41
+ ## Crawling and SEO
42
+
43
+ Both endpoints are crawlable so search engines can read your published content. Preview URLs are disallowed, so drafts never reach the index. The `robots.txt` rules:
44
+
45
+ ```
46
+ User-agent: *
47
+
48
+ # Allow crawling of the public project config and experience content endpoints
49
+ Allow: /api/v1/projects
50
+ Allow: /api/v1/projects/*/experiences/*/content
51
+
52
+ # Keep preview (draft) responses out of crawlers
53
+ Disallow: /api/v1/projects/*preview_token=
54
+
55
+ # Everything else (GraphQL, MCP, internal, ping) stays disallowed
56
+ Disallow: /
57
+ ```
58
+
59
+ See [Project Config](/delivery-api/project-config) and [Experience Content](/delivery-api/experience-content) for the endpoints themselves.
@@ -0,0 +1,56 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Project Config Endpoint
6
+
7
+ Fetch a project's published configuration: its experiences, steps, and the link from each step to the Content Entry that backs it.
8
+
9
+ ## Request
10
+
11
+ ```
12
+ GET /api/v1/projects/:projectId
13
+ ```
14
+
15
+ | Parameter | In | Description |
16
+ | --- | --- | --- |
17
+ | `projectId` | path | The project to fetch config for. |
18
+ | `preview_token` | query | Optional. Returns the preview (unpublished) config when present. |
19
+
20
+ ## Response
21
+
22
+ The response carries the project's experiences and steps. Each step includes `layoutContentId`, the reference to the Content Entry that backs that step's layout. Use it to know which experiences have content to fetch from the [Experience Content](/delivery-api/experience-content) endpoint.
23
+
24
+ | Field | Type | Description |
25
+ | --- | --- | --- |
26
+ | `id` | `string` | The project id. |
27
+ | `versionId` | `string \| null` | The published version id, or `null` on preview. |
28
+ | `steps[].layoutContentId` | `string \| null` | The Content Entry backing this step, or `null` when the step has no content. |
29
+
30
+ ## Published vs preview
31
+
32
+ On the published surface, `versionId` is the published version id. On the preview surface (`?preview_token=...`), `versionId` is `null` because there is no published version. See [Preview, Caching & Crawling](/delivery-api/preview-caching-crawling).
33
+
34
+ ## Example
35
+
36
+ ```bash
37
+ curl https://api.picked.app/api/v1/projects/proj_123
38
+ ```
39
+
40
+ ```json
41
+ {
42
+ "id": "proj_123",
43
+ "versionId": "ver_456",
44
+ "steps": [
45
+ {
46
+ "id": "step_1",
47
+ "key": "hero",
48
+ "name": "Hero",
49
+ "layoutName": "HeroLayout",
50
+ "layoutContentId": "entry_789"
51
+ }
52
+ ]
53
+ }
54
+ ```
55
+
56
+ Pass the config to `serialize()` on your server to produce an SSR snapshot. See [Server Snapshot](/sdk/ssr/serialize).
@@ -6,7 +6,9 @@ import { Screenshot } from '../../components/Screenshot'
6
6
 
7
7
  # Experiences
8
8
 
9
- An experience is a personalized UI flow delivered to visitors on your site. Each experience maps to a component registered in your application.
9
+ An experience is a personalized experience delivered to visitors on your site. Each experience maps to a component registered in your application.
10
+
11
+ Picked has two kinds of experience. A **Flow** is a multi-step, interactive experience rendered in the browser, covered on this page. A **Page** is a standalone, server-rendered experience backed by CMS content, built for SEO-critical landing pages. See [Pages](/content/pages) for that kind.
10
12
 
11
13
  <Screenshot
12
14
  route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/experiences"
@@ -8,6 +8,8 @@ import { Screenshot } from '../../components/Screenshot'
8
8
 
9
9
  A field is a data input shown to visitors inside a step. Fields collect information like names, email addresses, preferences, or selections.
10
10
 
11
+ These are experience Fields, which collect visitor input. They are distinct from the Content Fields that make up a CMS layout's schema, covered in [Page & Step Layouts](/content/layouts).
12
+
11
13
  <Screenshot
12
14
  route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/fields"
13
15
  alt="The fields list for a workspace on the Picked dashboard"
@@ -0,0 +1,143 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ import { Screenshot } from '../../../components/Screenshot'
6
+
7
+ # Build a Personalized Page
8
+
9
+ Build a personalized landing page with Picked: model it in the dashboard, publish it, and render it server-side in your app.
10
+
11
+ ## What you will build
12
+
13
+ A standalone, SEO-critical landing page whose content lives in Picked's CMS and renders on your server. You model the page's shape once, fill it with content, publish it, then fetch and render it in React or Svelte. The lifecycle from authoring to a hydrated page:
14
+
15
+ ```
16
+ Author (dashboard) -> Publish -> Delivery API (config + content)
17
+ -> serialize() on server -> Provider seeds client -> ExperienceSlot renders -> hydrate
18
+ ```
19
+
20
+ ## Model a page layout
21
+
22
+ A [Page Layout](/content/layouts) defines the fields your page can hold.
23
+
24
+ 1. Open your workspace and click **Models** in the sidebar.
25
+ 2. Click **New Model** and choose **Page Layout**.
26
+ 3. Enter a name and a **Key**, for example `page-hero`. The Key is a unique, stable identifier you use in SDK code and API references.
27
+ 4. Add a few fields: a **Single-line text** field `title`, a **Markdown** field `body`, and an **Asset** field `image`.
28
+ 5. Save the layout.
29
+
30
+ <Screenshot
31
+ route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/models"
32
+ alt="A page layout in the schema builder on the Picked dashboard"
33
+ />
34
+
35
+ See [Page & Step Layouts](/content/layouts) for every field type.
36
+
37
+ ## Create and fill the page
38
+
39
+ 1. Click **Experiences** in the sidebar, then **New Experience**.
40
+ 2. Choose the **Page** type and pick your `page-hero` layout.
41
+ 3. On the **Properties** tab, fill in `title`, `body`, and `image`. A [Content Entry](/content/content-entries) is created and linked on first save.
42
+ 4. Fix any per-field validation errors.
43
+ 5. Publish the workspace to make the page live.
44
+
45
+ See [Pages](/content/pages) for the full editor reference.
46
+
47
+ ## Fetch the config and content
48
+
49
+ Your server fetches two things from the [Delivery API](/delivery-api): the project config (which experiences and steps exist) and the content for the matching experience.
50
+
51
+ ```bash
52
+ # 1. Project config: find the steps and their content links
53
+ curl https://api.picked.app/api/v1/projects/proj_123
54
+
55
+ # 2. Experience content: the entry data backing the page
56
+ curl https://api.picked.app/api/v1/projects/proj_123/experiences/exp_123/content
57
+ ```
58
+
59
+ In practice you fetch the config, then let `serialize()` fetch the content for you. See [Project Config](/delivery-api/project-config) and [Experience Content](/delivery-api/experience-content).
60
+
61
+ ## Render server-side and hydrate
62
+
63
+ On the server, create a per-request client, call `serialize()` to produce a snapshot, render your app, and embed the snapshot. On the client, seed from the snapshot and hydrate.
64
+
65
+ ```ts
66
+ // server.ts
67
+ import { createClient } from '@picked/client'
68
+ import { serialize } from '@picked/client/server'
69
+
70
+ const client = createClient(options)
71
+ const snapshot = await serialize(client, {
72
+ projectConfig,
73
+ attributes: { location: { pathname: url.pathname } },
74
+ })
75
+ // Render your app, then embed `snapshot` in the HTML response.
76
+ ```
77
+
78
+ Render `<ExperienceSlot name="page" />` inside the provider, and register the component the Page maps to (here, `CMSPage`):
79
+
80
+ :::code-group
81
+
82
+ ```tsx [React]
83
+ import type { CreateClientOptions } from '@picked/client'
84
+ import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
85
+ import { adaptReactComponent, ExperienceSlot, PickedProvider } from '@picked/react'
86
+ import { reactFrameworkPlugin } from '@picked/react/plugins/framework'
87
+ import { useStepContent } from '@picked/react/hooks'
88
+
89
+ function CMSPage() {
90
+ const content = useStepContent()
91
+ const title = typeof content?.title === 'string' ? content.title : ''
92
+ return <h1>{title}</h1>
93
+ }
94
+
95
+ const options = {
96
+ projectId: 'proj_123',
97
+ components: { CMSPage: adaptReactComponent(CMSPage) },
98
+ plugins: [reactFrameworkPlugin({ components: {} }), dynamicExperiencesPlugin()],
99
+ } satisfies CreateClientOptions
100
+
101
+ export function Page({ snapshot }) {
102
+ return (
103
+ <PickedProvider options={options} snapshot={snapshot}>
104
+ <ExperienceSlot name="page" />
105
+ </PickedProvider>
106
+ )
107
+ }
108
+ ```
109
+
110
+ ```svelte [Svelte]
111
+ <script lang="ts">
112
+ import type { CreateClientOptions } from '@picked/client'
113
+ import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
114
+ import { adaptSvelteComponent, ExperienceSlot, setPickedContext } from '@picked/svelte'
115
+ import { svelteFrameworkPlugin } from '@picked/svelte/plugins/framework'
116
+ import CMSPage from './CMSPage.svelte'
117
+
118
+ const { snapshot } = $props()
119
+
120
+ const options = {
121
+ projectId: 'proj_123',
122
+ components: { CMSPage: adaptSvelteComponent(CMSPage) },
123
+ plugins: [svelteFrameworkPlugin({ components: {} }), dynamicExperiencesPlugin()],
124
+ } satisfies CreateClientOptions
125
+
126
+ setPickedContext({ options, snapshot })
127
+ </script>
128
+
129
+ <ExperienceSlot name="page" />
130
+ ```
131
+
132
+ :::
133
+
134
+ The provider seeds synchronously, so the server HTML and the client's first paint match, then the SDK hydrates. See [Server-Side Rendering](/sdk/ssr) for the full reference.
135
+
136
+ ## Verify SEO
137
+
138
+ Confirm the page is crawlable end to end:
139
+
140
+ 1. View the server response (for example with `curl` or your browser's view-source) and check the rendered HTML contains your `title` and `body`, not an empty shell.
141
+ 2. Confirm the content endpoint is reachable anonymously and returns your entry data.
142
+
143
+ Published Delivery API responses are crawlable; preview URLs are disallowed. See [Preview, Caching & Crawling](/delivery-api/preview-caching-crawling).
@@ -6,6 +6,8 @@ layout: docs
6
6
 
7
7
  Define experiences in code with `createExperience()`, or load them from the dashboard at runtime with the [Dynamic Experiences Plugin](./plugins/dynamic-experiences).
8
8
 
9
+ The execution flow below targets a DOM selector and renders into it. A Page experience instead renders through a named slot: target it to `.picked-slot-<name>` and render `<ExperienceSlot name="<name>" />` on your server. Use the slot path for SEO-critical, server-rendered Pages; use the selector path for browser-rendered Flows. See [Server-Side Rendering](/sdk/ssr) and [Slots & Placements](/sdk/ssr/slots-and-placements).
10
+
9
11
  ## Creating experiences
10
12
 
11
13
  ```ts
@@ -28,11 +28,21 @@ picked.init()
28
28
  ```
29
29
 
30
30
  ```ts [Server]
31
- Coming Soon...
31
+ import { createClient } from '@picked/client'
32
+ import { serialize } from '@picked/client/server'
33
+
34
+ const client = createClient({ projectId: 'your-project-id' })
35
+
36
+ const snapshot = await serialize(client, {
37
+ projectConfig,
38
+ attributes: { location: { pathname: '/promo' } },
39
+ })
32
40
  ```
33
41
 
34
42
  :::
35
43
 
44
+ The `@picked/client/server` entry holds the server-only helpers, such as `serialize()`, that produce an SSR snapshot. It is a separate entry point so browser bundles never include it. See [Server-Side Rendering](/sdk/ssr).
45
+
36
46
  :::tip
37
47
  If you’re running Picked in a browser environment, access the Picked client in the console with `window.Picked`
38
48
  :::
@@ -0,0 +1,62 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Server-Side Rendering
6
+
7
+ Render Picked Pages on your server for SEO, then hydrate them on the client. This page shows the end-to-end shape; the following pages cover each piece.
8
+
9
+ ## Why server-side rendering
10
+
11
+ Pages are SEO-critical, so their content must be in the HTML your server returns. Crawlers and social unfurls read that HTML directly and do not wait for client-side JavaScript. Rendering a Page on the server puts its content in the initial response, then the client takes over.
12
+
13
+ ## The flow
14
+
15
+ ```
16
+ projectConfig (Delivery API)
17
+ -> serialize(client, params) on the server -> PickedSnapshot
18
+ -> embed snapshot in HTML
19
+ -> client seeds from snapshot -> renders the same markup -> hydrates
20
+ ```
21
+
22
+ 1. Fetch your published `projectConfig` from the [Delivery API](/delivery-api).
23
+ 2. Call `serialize()` on the server. It evaluates which experiences match the URL, fetches their content, and returns a plain-JSON `PickedSnapshot`. See [Server Snapshot](/sdk/ssr/serialize).
24
+ 3. Embed the snapshot in your HTML payload.
25
+ 4. On the client, seed a Picked client from the snapshot so the first paint matches the server markup, then hydrate. See [Rendering & Hydration](/sdk/ssr/rendering-and-hydration).
26
+
27
+ Because the snapshot carries the exact config the server selected with and the memoized condition results, the client's first render reuses the server's decisions instead of re-evaluating them.
28
+
29
+ ## React vs Svelte at a glance
30
+
31
+ :::code-group
32
+
33
+ ```tsx [React]
34
+ import { PickedProvider, ExperienceSlot } from '@picked/react'
35
+
36
+ <PickedProvider options={options} snapshot={snapshot}>
37
+ <ExperienceSlot name="page" />
38
+ </PickedProvider>
39
+ ```
40
+
41
+ ```svelte [Svelte]
42
+ <script lang="ts">
43
+ import { ExperienceSlot, setPickedContext } from '@picked/svelte'
44
+
45
+ setPickedContext({ options, snapshot })
46
+ </script>
47
+
48
+ <ExperienceSlot name="page" />
49
+ ```
50
+
51
+ :::
52
+
53
+ ## The server entry point
54
+
55
+ The server helpers live in a dedicated entry so browser bundles never pull them in:
56
+
57
+ ```ts
58
+ import { serialize, registerProjectConfig } from '@picked/client/server'
59
+ import type { PickedSnapshot } from '@picked/client/server'
60
+ ```
61
+
62
+ Continue to [Server Snapshot](/sdk/ssr/serialize) for the `serialize()` reference, [Rendering & Hydration](/sdk/ssr/rendering-and-hydration) for wiring React and Svelte, and [Slots & Placements](/sdk/ssr/slots-and-placements) for how a slot selects experiences. New to the SDK? Start with [Installation](/sdk).