Preview changes

View as Markdown

Fern offers two ways to preview documentation changes:

Prerequisites

Install the following:

Local development

Run a local preview server to view documentation changes instantly with hot reload. Offline access is available after the first online run.

$ # Start preview server (from directory containing fern folder)
$ fern docs dev
$
$ # Or use a custom port
$ fern docs dev --port 3002

Your documentation will be available at http://localhost:3000 (default) or the port you specified. If you attempt to run Fern on a port that’s already in use, it will use the next available port.

Some features are disabled in local development:

  • Search
  • SEO (favicon, auto-generated meta tags, etc.)
  • Authentication

Generate shareable preview URLs to review and collaborate on documentation changes before publishing. Preview links aren’t indexed by search engines and don’t expire.

By default, each run generates a new URL with a unique UUID. The --id flag creates a stable, named preview link — rerunning with the same --id updates the existing preview in place.

$# Generate a preview link with a unique URL
$fern generate --docs --preview
$
$# Or use --id for a stable, named preview link
$fern generate --docs --preview --id my-feature
Example output
$# Without --id (unique UUID each time)
$[docs]: Published docs to https://fern-preview-c973a36e-337b-44f5-ab83-aab.docs.buildwithfern.com/learn
$
$# With --id my-feature (stable URL)
$[docs]: Published docs to https://fern-preview-my-feature.docs.buildwithfern.com/learn

When a preview with the same --id already exists, Fern prompts you to confirm the overwrite. This is skipped automatically in GitHub Actions, but for other CI environments (e.g., Azure Pipelines), use --force to skip the confirmation.

$fern generate --docs --preview --id my-feature --force

You can delete a preview deployment when it’s no longer needed:

$fern docs preview delete <url>

Automate with GitHub Actions

You can use a GitHub Actions workflow to automatically generate a preview URL when a pull request is opened. By passing --id with the branch name, every push to the same PR updates the same preview URL instead of creating a new one. The workflow posts a comment on the PR with the preview link and direct links to every page changed in the PR, so reviewers can jump straight to affected pages.

GitHub Actions bot comment on a pull request showing a named preview URL and direct links to changed documentation pages

If you set up your site using the guided UI or CLI quickstart, this workflow is automatically included in your repository. Otherwise, add it manually using the examples below.

These workflows require a FERN_TOKEN repository secret. If you used the guided workflow, this secret is added automatically. Otherwise, run fern token in your terminal to generate a token, then add it in your repository’s Settings > Secrets and variables > Actions with the name FERN_TOKEN.

You may need to re-run preview builds for any PRs that were opened before you configured the FERN_TOKEN.

.github/workflows/preview-docs.yml
1name: Preview Docs
2
3on:
4 pull_request:
5 types: [opened, synchronize, ready_for_review]
6 branches:
7 - main
8
9jobs:
10 run:
11 runs-on: ubuntu-latest
12 permissions:
13 pull-requests: write
14 contents: read
15 steps:
16 - name: Checkout repository
17 uses: actions/checkout@v4
18 with:
19 fetch-depth: 0
20
21 - name: Setup Fern CLI
22 uses: fern-api/setup-fern-cli@v1
23
24 - name: Generate preview URL
25 id: generate-docs
26 env:
27 FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
28 HEAD_REF: ${{ github.head_ref }}
29 run: |
30 OUTPUT=$(fern generate --docs --preview --id "$HEAD_REF" 2>&1) || true
31 echo "$OUTPUT"
32 URL=$(echo "$OUTPUT" | grep -oP 'Published docs to \K.*(?= \()')
33 echo "preview_url=$URL" >> $GITHUB_OUTPUT
34 echo "Preview URL: $URL"
35
36 - name: Get page links for changed MDX files
37 id: page-links
38 env:
39 FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
40 run: |
41 PREVIEW_URL="${{ steps.generate-docs.outputs.preview_url }}"
42 CHANGED_FILES=$(git diff --name-only origin/main...HEAD -- '*.mdx' 2>/dev/null || echo "")
43
44 if [ -z "$CHANGED_FILES" ] || [ -z "$PREVIEW_URL" ]; then
45 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
46 fi
47
48 BASE_URL=$(echo "$PREVIEW_URL" | grep -oP 'https?://[^/]+')
49
50 FILES_PARAM=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//')
51 RESPONSE=$(curl -sf -H "FERN_TOKEN: $FERN_TOKEN" "${PREVIEW_URL}/api/fern-docs/get-slug-for-file?files=${FILES_PARAM}" 2>/dev/null) || {
52 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
53 }
54
55 PAGE_LINKS=$(echo "$RESPONSE" | jq -r --arg url "$BASE_URL" \
56 '.mappings[] | select(.slug != null) | "- [\(.slug)](\($url)/\(.slug))"')
57
58 if [ -n "$PAGE_LINKS" ]; then
59 { echo "page_links<<EOF"; echo "$PAGE_LINKS"; echo "EOF"; } >> $GITHUB_OUTPUT
60 else
61 echo "page_links=" >> $GITHUB_OUTPUT
62 fi
63
64 - name: Create comment content
65 run: |
66 echo ":herb: **Preview your docs:** <${{ steps.generate-docs.outputs.preview_url }}>" > comment.md
67
68 if [ -n "${{ steps.page-links.outputs.page_links }}" ]; then
69 echo "" >> comment.md
70 echo "Here are the markdown pages you've updated:" >> comment.md
71 echo "${{ steps.page-links.outputs.page_links }}" >> comment.md
72 fi
73
74 - name: Post PR comment
75 uses: thollander/actions-comment-pull-request@v2.4.3
76 with:
77 filePath: comment.md
78 comment_tag: preview-docs
79 mode: upsert

If your repository accepts contributions from forks, use pull_request_target instead of pull_request to allow the workflow to access your FERN_TOKEN secret:

.github/workflows/preview-docs.yml
1name: Preview Docs
2
3on:
4 pull_request_target:
5 types: [opened, synchronize, ready_for_review]
6 branches:
7 - main
8
9jobs:
10 run:
11 runs-on: ubuntu-latest
12 permissions:
13 pull-requests: write
14 contents: read
15 steps:
16 - name: Checkout repository
17 uses: actions/checkout@v4
18 with:
19 fetch-depth: 0
20
21 - name: Checkout PR
22 run: |
23 git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-${{ github.event.pull_request.number }}
24 git checkout pr-${{ github.event.pull_request.number }}
25
26 - name: Setup Fern CLI
27 uses: fern-api/setup-fern-cli@v1
28
29 - name: Generate preview URL
30 id: generate-docs
31 env:
32 FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
33 HEAD_REF: ${{ github.head_ref }}
34 run: |
35 OUTPUT=$(fern generate --docs --preview --id "$HEAD_REF" 2>&1) || true
36 echo "$OUTPUT"
37 URL=$(echo "$OUTPUT" | grep -oP 'Published docs to \K.*(?= \()')
38 echo "preview_url=$URL" >> $GITHUB_OUTPUT
39 echo "Preview URL: $URL"
40
41 - name: Get page links for changed MDX files
42 id: page-links
43 env:
44 FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
45 run: |
46 PREVIEW_URL="${{ steps.generate-docs.outputs.preview_url }}"
47 CHANGED_FILES=$(git diff --name-only origin/main...HEAD -- '*.mdx' 2>/dev/null || echo "")
48
49 if [ -z "$CHANGED_FILES" ] || [ -z "$PREVIEW_URL" ]; then
50 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
51 fi
52
53 BASE_URL=$(echo "$PREVIEW_URL" | grep -oP 'https?://[^/]+')
54
55 FILES_PARAM=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//')
56 RESPONSE=$(curl -sf -H "FERN_TOKEN: $FERN_TOKEN" "${PREVIEW_URL}/api/fern-docs/get-slug-for-file?files=${FILES_PARAM}" 2>/dev/null) || {
57 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
58 }
59
60 PAGE_LINKS=$(echo "$RESPONSE" | jq -r --arg url "$BASE_URL" \
61 '.mappings[] | select(.slug != null) | "- [\(.slug)](\($url)/\(.slug))"')
62
63 if [ -n "$PAGE_LINKS" ]; then
64 { echo "page_links<<EOF"; echo "$PAGE_LINKS"; echo "EOF"; } >> $GITHUB_OUTPUT
65 else
66 echo "page_links=" >> $GITHUB_OUTPUT
67 fi
68
69 - name: Create comment content
70 run: |
71 echo ":herb: **Preview your docs:** <${{ steps.generate-docs.outputs.preview_url }}>" > comment.md
72
73 if [ -n "${{ steps.page-links.outputs.page_links }}" ]; then
74 echo "" >> comment.md
75 echo "Here are the markdown pages you've updated:" >> comment.md
76 echo "${{ steps.page-links.outputs.page_links }}" >> comment.md
77 fi
78
79 - name: Post PR comment
80 uses: thollander/actions-comment-pull-request@v2.4.3
81 with:
82 filePath: comment.md
83 comment_tag: preview-docs
84 mode: upsert