1- import React , { useState , useEffect } from 'react'
2- import { IconCheck , IconArrowLeft , IconArrowRight , IconRevert } from '@posthog/icons '
3- import OSButton from 'components/OSButton '
1+ import React from 'react'
2+ import { DecisionTree } from 'components/Docs/DecisionTree '
3+ import type { DecisionTreeQuestion , DecisionTreeRecommendation } from 'components/Docs/DecisionTree '
44
5- interface Answer {
6- platform ?: string
7- framework ?: string
8- server ?: string
9- }
10-
11- interface Recommendation {
12- method : string
13- path : string
14- reason : string
15- }
16-
17- interface Option {
18- value : string
19- label : string
20- }
21-
22- interface Question {
23- id : keyof Answer
24- question : string
25- description : string
26- condition ?: ( answers : Answer ) => boolean
27- options : Option [ ]
28- }
5+ const questions : DecisionTreeQuestion [ ] = [
6+ {
7+ id : 'platform' ,
8+ question : 'Where are you deploying?' ,
9+ description : 'Select your hosting platform or deployment target.' ,
10+ options : [
11+ { value : 'vercel' , label : 'Vercel' } ,
12+ { value : 'netlify' , label : 'Netlify' } ,
13+ { value : 'aws' , label : 'AWS' } ,
14+ { value : 'cloudflare' , label : 'Cloudflare' } ,
15+ { value : 'railway' , label : 'Railway' } ,
16+ { value : 'self-hosted' , label : 'Self-hosted server' } ,
17+ { value : 'other' , label : 'Other / Framework-specific' } ,
18+ ] ,
19+ } ,
20+ {
21+ id : 'framework' ,
22+ question : 'What framework are you using?' ,
23+ description : 'Some frameworks have built-in proxy capabilities.' ,
24+ condition : ( answers ) =>
25+ answers . platform === 'vercel' || answers . platform === 'netlify' || answers . platform === 'other' ,
26+ options : [
27+ { value : 'nextjs' , label : 'Next.js' } ,
28+ { value : 'nuxt' , label : 'Nuxt' } ,
29+ { value : 'sveltekit' , label : 'SvelteKit' } ,
30+ { value : 'remix' , label : 'Remix' } ,
31+ { value : 'other' , label : 'Other / Plain HTML' } ,
32+ ] ,
33+ } ,
34+ {
35+ id : 'server' ,
36+ question : 'What server are you using?' ,
37+ description : 'Select your server or container orchestration platform.' ,
38+ condition : ( answers ) => answers . platform === 'self-hosted' ,
39+ options : [
40+ { value : 'nginx' , label : 'nginx' } ,
41+ { value : 'caddy' , label : 'Caddy' } ,
42+ { value : 'kubernetes' , label : 'Kubernetes' } ,
43+ { value : 'node' , label : 'Node.js' } ,
44+ { value : 'other' , label : 'Other' } ,
45+ ] ,
46+ } ,
47+ ]
2948
30- const getRecommendation = ( answers : Answer ) : Recommendation => {
49+ const getRecommendation = ( answers : Record < string , string > ) : DecisionTreeRecommendation => {
3150 if ( answers . platform === 'vercel' ) {
3251 if ( answers . framework === 'nextjs' ) {
3352 return {
34- method : 'Next.js rewrites' ,
53+ title : 'We recommend: Next.js rewrites' ,
3554 path : '/docs/advanced/proxy/nextjs' ,
3655 reason : 'Next.js rewrites are built into the framework and work seamlessly on Vercel with minimal configuration.' ,
3756 }
3857 }
3958 return {
40- method : 'Vercel rewrites' ,
59+ title : 'We recommend: Vercel rewrites' ,
4160 path : '/docs/advanced/proxy/vercel' ,
4261 reason : 'Vercel rewrites work for any framework on their platform with just a simple vercel.json configuration.' ,
4362 }
4463 }
4564
4665 if ( answers . platform === 'netlify' ) {
4766 return {
48- method : 'Netlify redirects' ,
67+ title : 'We recommend: Netlify redirects' ,
4968 path : '/docs/advanced/proxy/netlify' ,
5069 reason : "Netlify's redirect rules work with any framework through a simple netlify.toml or _redirects file." ,
5170 }
5271 }
5372
5473 if ( answers . platform === 'aws' ) {
5574 return {
56- method : 'AWS CloudFront' ,
75+ title : 'We recommend: AWS CloudFront' ,
5776 path : '/docs/advanced/proxy/cloudfront' ,
5877 reason : 'CloudFront integrates seamlessly with AWS services and provides excellent performance with global edge locations.' ,
5978 }
6079 }
6180
6281 if ( answers . platform === 'cloudflare' ) {
6382 return {
64- method : 'Cloudflare Workers' ,
83+ title : 'We recommend: Cloudflare Workers' ,
6584 path : '/docs/advanced/proxy/cloudflare' ,
6685 reason : "Workers run at the edge across Cloudflare's global network and work on the free tier." ,
6786 }
6887 }
6988
7089 if ( answers . platform === 'railway' ) {
7190 return {
72- method : 'Railway template' ,
91+ title : 'We recommend: Railway template' ,
7392 path : '/docs/advanced/proxy/railway' ,
7493 reason : 'Railway offers a one-click deployment template that sets up a PostHog proxy instantly.' ,
7594 }
@@ -78,28 +97,28 @@ const getRecommendation = (answers: Answer): Recommendation => {
7897 if ( answers . platform === 'self-hosted' ) {
7998 if ( answers . server === 'kubernetes' ) {
8099 return {
81- method : 'Kubernetes Ingress' ,
100+ title : 'We recommend: Kubernetes Ingress' ,
82101 path : '/docs/advanced/proxy/kubernetes-ingress-controller' ,
83102 reason : 'Using an Ingress Controller is the Kubernetes-native way to handle routing and proxy requests.' ,
84103 }
85104 }
86105 if ( answers . server === 'nginx' ) {
87106 return {
88- method : 'nginx' ,
107+ title : 'We recommend: nginx' ,
89108 path : '/docs/advanced/proxy/nginx' ,
90109 reason : 'nginx is a battle-tested, high-performance reverse proxy perfect for self-hosted setups.' ,
91110 }
92111 }
93112 if ( answers . server === 'caddy' ) {
94113 return {
95- method : 'Caddy' ,
114+ title : 'We recommend: Caddy' ,
96115 path : '/docs/advanced/proxy/caddy' ,
97116 reason : 'Caddy is a modern web server with automatic HTTPS that is simpler to configure than traditional servers.' ,
98117 }
99118 }
100119 if ( answers . server === 'node' ) {
101120 return {
102- method : 'Node.js HTTP' ,
121+ title : 'We recommend: Node.js HTTP' ,
103122 path : '/docs/advanced/proxy/node' ,
104123 reason : 'Using node:http lets you integrate the proxy directly into your existing Node.js application.' ,
105124 }
@@ -108,217 +127,37 @@ const getRecommendation = (answers: Answer): Recommendation => {
108127
109128 if ( answers . framework === 'nuxt' ) {
110129 return {
111- method : 'Nuxt server routes' ,
130+ title : 'We recommend: Nuxt server routes' ,
112131 path : '/docs/advanced/proxy/nuxt' ,
113132 reason : "Server routes are Nuxt's way to intercept and handle requests on the server side." ,
114133 }
115134 }
116135
117136 if ( answers . framework === 'sveltekit' ) {
118137 return {
119- method : 'SvelteKit server hooks' ,
138+ title : 'We recommend: SvelteKit server hooks' ,
120139 path : '/docs/advanced/proxy/sveltekit' ,
121140 reason : "Server hooks are SvelteKit's way to intercept and handle requests on the server side." ,
122141 }
123142 }
124143
125144 if ( answers . framework === 'remix' ) {
126145 return {
127- method : 'Remix resource routes' ,
146+ title : 'We recommend: Remix resource routes' ,
128147 path : '/docs/advanced/proxy/remix' ,
129148 reason : "Remix's resource routes with splat routing provide a clean, server-side way to proxy requests." ,
130149 }
131150 }
132151
133152 return {
134- method : 'Managed reverse proxy' ,
153+ title : 'We recommend: Managed reverse proxy' ,
135154 path : '/docs/advanced/proxy/managed-reverse-proxy' ,
136155 reason : "Based on your setup, we recommend starting with the managed proxy. It's the simplest option that works everywhere." ,
137156 }
138157}
139158
140- const questions : Question [ ] = [
141- {
142- id : 'platform' ,
143- question : 'Where are you deploying?' ,
144- description : 'Select your hosting platform or deployment target.' ,
145- options : [
146- { value : 'vercel' , label : 'Vercel' } ,
147- { value : 'netlify' , label : 'Netlify' } ,
148- { value : 'aws' , label : 'AWS' } ,
149- { value : 'cloudflare' , label : 'Cloudflare' } ,
150- { value : 'railway' , label : 'Railway' } ,
151- { value : 'self-hosted' , label : 'Self-hosted server' } ,
152- { value : 'other' , label : 'Other / Framework-specific' } ,
153- ] ,
154- } ,
155- {
156- id : 'framework' ,
157- question : 'What framework are you using?' ,
158- description : 'Some frameworks have built-in proxy capabilities.' ,
159- condition : ( answers ) =>
160- answers . platform === 'vercel' || answers . platform === 'netlify' || answers . platform === 'other' ,
161- options : [
162- { value : 'nextjs' , label : 'Next.js' } ,
163- { value : 'nuxt' , label : 'Nuxt' } ,
164- { value : 'sveltekit' , label : 'SvelteKit' } ,
165- { value : 'remix' , label : 'Remix' } ,
166- { value : 'other' , label : 'Other / Plain HTML' } ,
167- ] ,
168- } ,
169- {
170- id : 'server' ,
171- question : 'What server are you using?' ,
172- description : 'Select your server or container orchestration platform.' ,
173- condition : ( answers ) => answers . platform === 'self-hosted' ,
174- options : [
175- { value : 'nginx' , label : 'nginx' } ,
176- { value : 'caddy' , label : 'Caddy' } ,
177- { value : 'kubernetes' , label : 'Kubernetes' } ,
178- { value : 'node' , label : 'Node.js' } ,
179- { value : 'other' , label : 'Other' } ,
180- ] ,
181- } ,
182- ]
183-
184159const ProxyDecisionTree : React . FC = ( ) => {
185- const [ step , setStep ] = useState ( 0 )
186- const [ answers , setAnswers ] = useState < Answer > ( { } )
187- const [ recommendation , setRecommendation ] = useState < Recommendation | null > ( null )
188-
189- const currentQuestion = questions [ step ]
190-
191- const handleAnswer = ( questionId : keyof Answer , value : string ) => {
192- const newAnswers = { ...answers , [ questionId ] : value }
193- setAnswers ( newAnswers )
194-
195- const nextQuestions = questions . slice ( step + 1 )
196- const hasMoreQuestions = nextQuestions . some ( ( q ) => ! q . condition || q . condition ( newAnswers ) )
197-
198- if ( hasMoreQuestions ) {
199- setStep ( step + 1 )
200- } else {
201- const rec = getRecommendation ( newAnswers )
202- setRecommendation ( rec )
203- }
204- }
205-
206- const reset = ( ) => {
207- setStep ( 0 )
208- setAnswers ( { } )
209- setRecommendation ( null )
210- }
211-
212- const handleBack = ( ) => {
213- // Find the previous question that was actually answered
214- let prevStep = step - 1
215- while ( prevStep >= 0 ) {
216- const prevQuestion = questions [ prevStep ]
217- if ( ! prevQuestion . condition || prevQuestion . condition ( answers ) ) {
218- break
219- }
220- prevStep --
221- }
222- // Clear the answer for the question we're going back to
223- const prevQuestion = questions [ prevStep ]
224- if ( prevQuestion ) {
225- const newAnswers = { ...answers }
226- delete newAnswers [ prevQuestion . id ]
227- // Also clear any answers from questions after this one
228- questions . slice ( prevStep + 1 ) . forEach ( ( q ) => {
229- delete newAnswers [ q . id ]
230- } )
231- setAnswers ( newAnswers )
232- }
233- setStep ( prevStep )
234- }
235-
236- useEffect ( ( ) => {
237- if ( currentQuestion && currentQuestion . condition && ! currentQuestion . condition ( answers ) ) {
238- const nextStep = questions . findIndex ( ( q , i ) => i > step && ( ! q . condition || q . condition ( answers ) ) )
239- if ( nextStep !== - 1 ) {
240- setStep ( nextStep )
241- } else {
242- const rec = getRecommendation ( answers )
243- setRecommendation ( rec )
244- }
245- }
246- } , [ step , answers ] )
247-
248- if ( recommendation ) {
249- return (
250- < div className = "border border-primary rounded-md p-5 bg-accent my-4" >
251- < div className = "flex items-start gap-4" >
252- < div className = "w-10 h-10 rounded-full bg-green flex items-center justify-center flex-shrink-0" >
253- < IconCheck className = "w-5 h-5 text-white" />
254- </ div >
255- < div className = "flex-1" >
256- < h4 className = "text-lg font-bold m-0 mb-2" > We recommend: { recommendation . method } </ h4 >
257- < p className = "text-[15px] opacity-75 m-0 mb-4" > { recommendation . reason } </ p >
258-
259- < div className = "flex flex-wrap gap-2" >
260- < OSButton
261- asLink
262- to = { recommendation . path }
263- variant = "primary"
264- size = "md"
265- icon = { < IconArrowRight className = "w-4 h-4" /> }
266- iconPosition = "right"
267- >
268- Follow the guide
269- </ OSButton >
270- < OSButton
271- onClick = { reset }
272- variant = "secondary"
273- size = "md"
274- icon = { < IconRevert className = "w-4 h-4" /> }
275- >
276- Start over
277- </ OSButton >
278- </ div >
279- </ div >
280- </ div >
281- </ div >
282- )
283- }
284-
285- if ( ! currentQuestion ) return null
286-
287- return (
288- < div className = "border border-primary rounded-md p-5 bg-accent my-4" >
289- { step > 0 && (
290- < OSButton
291- onClick = { handleBack }
292- variant = "default"
293- size = "sm"
294- icon = { < IconArrowLeft className = "size-4" /> }
295- className = "mb-3 opacity-60 hover:opacity-100"
296- >
297- Back
298- </ OSButton >
299- ) }
300-
301- < div className = "mb-4" >
302- < h4 className = "text-lg font-bold m-0" > { currentQuestion . question } </ h4 >
303- < p className = "text-[15px] opacity-75 m-0 mt-1" > { currentQuestion . description } </ p >
304- </ div >
305-
306- < div className = "grid sm:grid-cols-2 gap-2" >
307- { currentQuestion . options . map ( ( option ) => (
308- < OSButton
309- key = { option . value }
310- onClick = { ( ) => handleAnswer ( currentQuestion . id , option . value ) }
311- variant = "secondary"
312- size = "md"
313- width = "full"
314- align = "left"
315- >
316- { option . label }
317- </ OSButton >
318- ) ) }
319- </ div >
320- </ div >
321- )
160+ return < DecisionTree questions = { questions } getRecommendation = { getRecommendation } />
322161}
323162
324163export default ProxyDecisionTree
0 commit comments