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. Each preview link includes a unique UUID and isn’t indexed by search engines. Links don’t expire.

$fern generate --docs --preview
Example output
$[docs]: Found 0 errors and 1 warnings. Run fern check --warnings to print out the warnings.
$[docs]: Published docs to https://fern-preview-c973a36e-337b-44f5-ab83-aab.docs.buildwithfern.com/learn
$┌─
$│ ✓ docs.example.com
$└─

You can also list all preview deployments for your organization and delete a preview deployment when it’s no longer needed:

$fern docs preview list
$fern docs preview delete <url>

Automate with GitHub Actions

If you set up your site using the guided UI or CLI quickstart, preview workflow files are automatically included in your repository. Otherwise, add them 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 run: |
29 OUTPUT=$(fern generate --docs --preview 2>&1) || true
30 echo "$OUTPUT"
31 URL=$(echo "$OUTPUT" | grep -oP 'Published docs to \K.*(?= \()')
32 echo "preview_url=$URL" >> $GITHUB_OUTPUT
33 echo "Preview URL: $URL"
34
35 - name: Get page links for changed MDX files
36 id: page-links
37 env:
38 FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
39 run: |
40 PREVIEW_URL="${{ steps.generate-docs.outputs.preview_url }}"
41 CHANGED_FILES=$(git diff --name-only origin/main...HEAD -- '*.mdx' 2>/dev/null || echo "")
42
43 if [ -z "$CHANGED_FILES" ] || [ -z "$PREVIEW_URL" ]; then
44 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
45 fi
46
47 BASE_URL=$(echo "$PREVIEW_URL" | grep -oP 'https?://[^/]+')
48
49 FILES_PARAM=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//')
50 RESPONSE=$(curl -sf -H "FERN_TOKEN: $FERN_TOKEN" "${PREVIEW_URL}/api/fern-docs/get-slug-for-file?files=${FILES_PARAM}" 2>/dev/null) || {
51 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
52 }
53
54 PAGE_LINKS=$(echo "$RESPONSE" | jq -r --arg url "$BASE_URL" \
55 '.mappings[] | select(.slug != null) | "- [\(.slug)](\($url)/\(.slug))"')
56
57 if [ -n "$PAGE_LINKS" ]; then
58 { echo "page_links<<EOF"; echo "$PAGE_LINKS"; echo "EOF"; } >> $GITHUB_OUTPUT
59 else
60 echo "page_links=" >> $GITHUB_OUTPUT
61 fi
62
63 - name: Create comment content
64 run: |
65 echo ":herb: **Preview your docs:** <${{ steps.generate-docs.outputs.preview_url }}>" > comment.md
66
67 if [ -n "${{ steps.page-links.outputs.page_links }}" ]; then
68 echo "" >> comment.md
69 echo "Here are the markdown pages you've updated:" >> comment.md
70 echo "${{ steps.page-links.outputs.page_links }}" >> comment.md
71 fi
72
73 - name: Post PR comment
74 uses: thollander/actions-comment-pull-request@v2.4.3
75 with:
76 filePath: comment.md
77 comment_tag: preview-docs
78 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 run: |
34 OUTPUT=$(fern generate --docs --preview 2>&1) || true
35 echo "$OUTPUT"
36 URL=$(echo "$OUTPUT" | grep -oP 'Published docs to \K.*(?= \()')
37 echo "preview_url=$URL" >> $GITHUB_OUTPUT
38 echo "Preview URL: $URL"
39
40 - name: Get page links for changed MDX files
41 id: page-links
42 env:
43 FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
44 run: |
45 PREVIEW_URL="${{ steps.generate-docs.outputs.preview_url }}"
46 CHANGED_FILES=$(git diff --name-only origin/main...HEAD -- '*.mdx' 2>/dev/null || echo "")
47
48 if [ -z "$CHANGED_FILES" ] || [ -z "$PREVIEW_URL" ]; then
49 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
50 fi
51
52 BASE_URL=$(echo "$PREVIEW_URL" | grep -oP 'https?://[^/]+')
53
54 FILES_PARAM=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//')
55 RESPONSE=$(curl -sf -H "FERN_TOKEN: $FERN_TOKEN" "${PREVIEW_URL}/api/fern-docs/get-slug-for-file?files=${FILES_PARAM}" 2>/dev/null) || {
56 echo "page_links=" >> $GITHUB_OUTPUT; exit 0
57 }
58
59 PAGE_LINKS=$(echo "$RESPONSE" | jq -r --arg url "$BASE_URL" \
60 '.mappings[] | select(.slug != null) | "- [\(.slug)](\($url)/\(.slug))"')
61
62 if [ -n "$PAGE_LINKS" ]; then
63 { echo "page_links<<EOF"; echo "$PAGE_LINKS"; echo "EOF"; } >> $GITHUB_OUTPUT
64 else
65 echo "page_links=" >> $GITHUB_OUTPUT
66 fi
67
68 - name: Create comment content
69 run: |
70 echo ":herb: **Preview your docs:** <${{ steps.generate-docs.outputs.preview_url }}>" > comment.md
71
72 if [ -n "${{ steps.page-links.outputs.page_links }}" ]; then
73 echo "" >> comment.md
74 echo "Here are the markdown pages you've updated:" >> comment.md
75 echo "${{ steps.page-links.outputs.page_links }}" >> comment.md
76 fi
77
78 - name: Post PR comment
79 uses: thollander/actions-comment-pull-request@v2.4.3
80 with:
81 filePath: comment.md
82 comment_tag: preview-docs
83 mode: upsert