Exception: ReactOnRails::PrerenderError

Inherits:
Error
  • Object
show all
Defined in:
lib/react_on_rails/prerender_error.rb,
sig/react_on_rails/prerender_error.rbs

Constant Summary collapse

MAX_ERROR_SNIPPET_TO_LOG =

Returns:

  • (Integer)
1000

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(component_name: nil, err: nil, props: nil, js_code: nil, console_messages: nil) ⇒ PrerenderError

err might be nil if JS caught the error

Parameters:

  • component_name: (String, nil) (defaults to: nil)
  • err: (StandardError, nil) (defaults to: nil)
  • props: (String, nil) (defaults to: nil)
  • js_code: (String, nil) (defaults to: nil)
  • console_messages: (String, nil) (defaults to: nil)


14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/react_on_rails/prerender_error.rb', line 14

def initialize(component_name: nil, err: nil, props: nil,
               js_code: nil, console_messages: nil)
  @component_name = component_name
  @err = err
  @props = props
  @js_code = js_code
  @console_messages = console_messages

  backtrace, message = calc_message(component_name, console_messages, err, js_code, props)

  super([message, backtrace].compact.join("\n"))
end

Instance Attribute Details

#component_nameString? (readonly)

TODO: Consider remove providing original err as already have access to self.cause http://blog.honeybadger.io/nested-errors-in-ruby-with-exception-cause/

Returns:

  • (String, nil)


11
12
13
# File 'lib/react_on_rails/prerender_error.rb', line 11

def component_name
  @component_name
end

#console_messagesString? (readonly)

TODO: Consider remove providing original err as already have access to self.cause http://blog.honeybadger.io/nested-errors-in-ruby-with-exception-cause/

Returns:

  • (String, nil)


11
12
13
# File 'lib/react_on_rails/prerender_error.rb', line 11

def console_messages
  @console_messages
end

#errStandardError? (readonly)

TODO: Consider remove providing original err as already have access to self.cause http://blog.honeybadger.io/nested-errors-in-ruby-with-exception-cause/

Returns:

  • (StandardError, nil)


11
12
13
# File 'lib/react_on_rails/prerender_error.rb', line 11

def err
  @err
end

#js_codeString? (readonly)

TODO: Consider remove providing original err as already have access to self.cause http://blog.honeybadger.io/nested-errors-in-ruby-with-exception-cause/

Returns:

  • (String, nil)


11
12
13
# File 'lib/react_on_rails/prerender_error.rb', line 11

def js_code
  @js_code
end

#propsString? (readonly)

TODO: Consider remove providing original err as already have access to self.cause http://blog.honeybadger.io/nested-errors-in-ruby-with-exception-cause/

Returns:

  • (String, nil)


11
12
13
# File 'lib/react_on_rails/prerender_error.rb', line 11

def props
  @props
end

Instance Method Details

#build_troubleshooting_suggestions(component_name, err, console_messages) ⇒ String

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

Parameters:

  • component_name (String, nil)
  • err (StandardError, nil)
  • console_messages (String, nil)

Returns:

  • (String)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/react_on_rails/prerender_error.rb', line 107

def build_troubleshooting_suggestions(component_name, err, console_messages)
  suggestions = []

  # Check for common error patterns
  if err&.message&.include?("window is not defined") || console_messages&.include?("window is not defined")
    suggestions << <<~SUGGESTION
      1. Browser API used on server - wrap with client-side check:
         #{Rainbow("if (typeof window !== 'undefined') { ... }").cyan}
    SUGGESTION
  end

  if err&.message&.include?("document is not defined") || console_messages&.include?("document is not defined")
    suggestions << <<~SUGGESTION
      1. DOM API used on server - use React refs or useEffect:
         #{Rainbow('useEffect(() => { /* DOM operations here */ }, [])').cyan}
    SUGGESTION
  end

  if err&.message&.include?("Cannot read") || err&.message&.include?("undefined")
    suggestions << <<~SUGGESTION
      1. Check for null/undefined values in props
      2. Add default props or use optional chaining:
         #{Rainbow("props.data?.value || 'default'").cyan}
    SUGGESTION
  end

  if err&.message&.include?("Hydration") || console_messages&.include?("Hydration")
    suggestions << <<~SUGGESTION
      1. Server and client render mismatch - ensure consistent:
         - Random values (use seed from props)
         - Date/time values (pass from server)
         - User agent checks (avoid or use props)
    SUGGESTION
  end

  # Generic suggestions
  suggestions << <<~SUGGESTION
    • Temporarily disable SSR to isolate the issue:
      #{Rainbow('prerender: false').cyan} in your view helper
    • Check server logs for detailed errors:
      #{Rainbow('tail -f log/development.log').cyan}
    • Verify component registration:
      #{Rainbow("ReactOnRails.register({ #{component_name}: #{component_name} })").cyan}
    • Ensure server bundle is up to date:
      #{Rainbow('bin/shakapacker').cyan} or #{Rainbow('yarn run build:server').cyan}
  SUGGESTION

  suggestions.join("\n")
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
end

#calc_message(component_name, console_messages, err, js_code, props) ⇒ [String?, String]

rubocop:disable Metrics/AbcSize

Parameters:

  • component_name (String, nil)
  • console_messages (String, nil)
  • err (StandardError, nil)
  • js_code (String, nil)
  • props (String, nil)

Returns:

  • ([String?, String])


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/react_on_rails/prerender_error.rb', line 51

def calc_message(component_name, console_messages, err, js_code, props)
  header = Rainbow("❌ React on Rails Server Rendering Error").red.bright
  message = +"#{header}\n\n"

  message << Rainbow("Component: #{component_name}").yellow << "\n\n"

  if err
    message << Rainbow("Error Details:").red.bright << "\n"
    message << <<~MSG
      #{err.inspect}

    MSG

    backtrace = formatted_backtrace(err)
  else
    backtrace = nil
  end

  # Add props information
  message << Rainbow("Props:").blue.bright << "\n"
  message << "#{Utils.smart_trim(props, MAX_ERROR_SNIPPET_TO_LOG)}\n\n"

  # Add code snippet
  message << Rainbow("JavaScript Code:").blue.bright << "\n"
  message << "#{Utils.smart_trim(js_code, MAX_ERROR_SNIPPET_TO_LOG)}\n\n"

  if console_messages && console_messages.strip.present?
    message << Rainbow("Console Output:").magenta.bright << "\n"
    message << "#{console_messages}\n\n"
  end

  # Add actionable suggestions
  message << Rainbow("💡 Troubleshooting Steps:").yellow.bright << "\n"
  message << build_troubleshooting_suggestions(component_name, err, console_messages)

  # Add help and support information
  message << "\n#{Utils.default_troubleshooting_section}\n"

  [backtrace, message]
  # rubocop:enable Metrics/AbcSize
end

#raven_contextHash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


31
32
33
# File 'lib/react_on_rails/prerender_error.rb', line 31

def raven_context
  to_error_context
end

#to_error_contextHash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/react_on_rails/prerender_error.rb', line 35

def to_error_context
  result = {
    component_name:,
    err:,
    props:,
    js_code:,
    console_messages:
  }

  result.merge!(err.to_error_context) if err.respond_to?(:to_error_context)
  result
end

#to_honeybadger_contextHash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


27
28
29
# File 'lib/react_on_rails/prerender_error.rb', line 27

def to_honeybadger_context
  to_error_context
end