Skip to content

Commit f2f4a3e

Browse files
authored
Merge pull request #1997 from microlinkhq/improve-landings
chore: improve landings and tools
2 parents e51c679 + 3a639eb commit f2f4a3e

File tree

15 files changed

+294
-241
lines changed

15 files changed

+294
-241
lines changed

data/git-timestamps-modified.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@
460460
"src/pages/insights.js": "2026-02-23T17:35:57.000Z",
461461
"src/pages/integrations.js": "2020-07-29T16:14:34.000Z",
462462
"src/pages/logo.js": "2026-02-26T11:40:49.000Z",
463-
"src/pages/markdown.js": "2026-03-26T21:07:13.000Z",
463+
"src/pages/markdown.js": "2026-03-31T12:49:10.000Z",
464464
"src/pages/meta.js": "2026-01-24T13:05:07.000Z",
465465
"src/pages/metadata.js": "2026-02-19T21:48:11.000Z",
466466
"src/pages/mql.js": "2019-06-17T07:48:43.000Z",
@@ -471,11 +471,11 @@
471471
"src/pages/payment/index.js": "2026-02-16T22:30:19.000Z",
472472
"src/pages/payment/success.js": "2019-08-29T22:34:09.000Z",
473473
"src/pages/payment/update.js": "2026-02-16T22:30:19.000Z",
474-
"src/pages/pdf.js": "2026-03-30T13:27:46.000Z",
474+
"src/pages/pdf.js": "2026-03-31T15:32:46.000Z",
475475
"src/pages/privacy.js": "2018-10-08T20:58:37.000Z",
476476
"src/pages/privacy.md": "2025-07-01T19:16:55.000Z",
477477
"src/pages/recipes.js": "2026-01-20T18:07:04.000Z",
478-
"src/pages/screenshot.js": "2026-03-24T11:21:41.000Z",
478+
"src/pages/screenshot.js": "2026-03-31T15:30:44.000Z",
479479
"src/pages/sdk.js": "2026-02-23T17:40:34.000Z",
480480
"src/pages/security.md": "2025-07-01T19:52:12.000Z",
481481
"src/pages/sharing-debugger.js": "2026-01-14T10:31:56.000Z",
@@ -487,15 +487,15 @@
487487
"src/pages/terms.js": "2018-08-20T15:55:21.000Z",
488488
"src/pages/tools.js": "2026-03-28T16:37:55.000Z",
489489
"src/pages/tools/sharing-debugger.js": "2026-03-18T14:10:19.000Z",
490-
"src/pages/tools/url-to-markdown.js": "2026-03-26T10:43:10.000Z",
491-
"src/pages/tools/website-screenshot.js": "2026-03-18T18:34:05.000Z",
492-
"src/pages/tools/website-screenshot/animated.js": "2026-03-20T11:52:16.000Z",
493-
"src/pages/tools/website-screenshot/bulk.js": "2026-03-18T18:44:08.000Z",
494-
"src/pages/tools/website-screenshot/full-page.js": "2026-03-18T18:44:03.000Z",
495-
"src/pages/tools/website-screenshot/mobile.js": "2026-03-18T18:44:03.000Z",
496-
"src/pages/tools/website-to-pdf.js": "2026-03-29T18:14:54.000Z",
490+
"src/pages/tools/url-to-markdown.js": "2026-03-31T12:49:10.000Z",
491+
"src/pages/tools/website-screenshot.js": "2026-03-31T12:47:52.000Z",
492+
"src/pages/tools/website-screenshot/animated.js": "2026-03-31T12:49:10.000Z",
493+
"src/pages/tools/website-screenshot/bulk.js": "2026-03-31T12:49:10.000Z",
494+
"src/pages/tools/website-screenshot/full-page.js": "2026-03-31T12:49:10.000Z",
495+
"src/pages/tools/website-screenshot/mobile.js": "2026-03-31T12:49:10.000Z",
496+
"src/pages/tools/website-to-pdf.js": "2026-03-31T12:49:10.000Z",
497497
"src/pages/tools/website-to-pdf/batch.js": "2026-03-28T10:48:24.000Z",
498-
"src/pages/tools/website-to-pdf/bulk.js": "2026-03-29T18:14:54.000Z",
498+
"src/pages/tools/website-to-pdf/bulk.js": "2026-03-31T12:49:10.000Z",
499499
"src/pages/tos.js": "2018-10-08T20:58:37.000Z",
500500
"src/pages/tos.md": "2022-11-27T09:53:11.000Z",
501501
"src/pages/user-agents.js": "2026-02-02T11:46:29.000Z"

src/components/pages/screenshot/index.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import Spinner from 'components/elements/Spinner'
2727
import Subhead from 'components/elements/Subhead'
2828
import Text from 'components/elements/Text'
2929

30+
import {
31+
ApiErrorTitle,
32+
ApiErrorBody
33+
} from 'components/patterns/ApiError/ApiError'
34+
import { getErrorMeta } from 'helpers/api-error'
3035
import ArrowLink from 'components/patterns/ArrowLink'
3136
import Caption from 'components/patterns/Caption/Caption'
3237
import Tooltip from 'components/patterns/Tooltip/Tooltip'
@@ -246,7 +251,7 @@ export const PreviewCanvas = styled(Box)`
246251
overflow: 'hidden',
247252
position: 'relative'
248253
})}
249-
background: #f1f5f9;
254+
background: #fcfcfc;
250255
`
251256

252257
export const ViewportCard = styled(Box)`
@@ -995,18 +1000,17 @@ export const PreviewDisplay = ({
9951000
})}
9961001
>
9971002
<Text css={theme({ color: 'fullscreen', fontSize: 3, pb: 3 })}>
998-
{error?.statusCode === 429 && (
999-
<>
1000-
You've reached your free daily limit.
1001-
<Text css={theme({ fontSize: 2, color: 'black60' })}>
1002-
We allow 50 requests per day for free users.
1003-
</Text>
1004-
</>
1005-
)}
1006-
{error?.statusCode !== 429 &&
1007-
(error?.message || 'Something went wrong. Please try again.')}
1003+
<ApiErrorTitle code={error?.code} />
1004+
<Text css={theme({ fontSize: 2, color: 'black60', pt: 2 })}>
1005+
<ApiErrorBody
1006+
code={error?.code}
1007+
fallback={
1008+
error?.message || 'Something went wrong. Please try again.'
1009+
}
1010+
/>
1011+
</Text>
10081012
</Text>
1009-
{error?.statusCode !== 429 && (
1013+
{getErrorMeta(error?.code).showRetry && (
10101014
<Button onClick={onRetry}>
10111015
<Caps css={theme({ fontSize: 0 })}>Try again</Caps>
10121016
</Button>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react'
2+
import { Link } from 'components/elements/Link'
3+
import { getErrorMeta } from 'helpers/api-error'
4+
5+
const PricingLink = ({ children, ...props }) => (
6+
<Link href='/#pricing' {...props}>
7+
{children}
8+
</Link>
9+
)
10+
11+
const FRAGMENTS = {
12+
ERATE: LinkComponent => (
13+
<>
14+
You&#8217;ve reached your daily request limit. Wait for the quota to reset
15+
or <LinkComponent>upgrade your plan</LinkComponent> for higher limits.
16+
</>
17+
),
18+
ETIMEOUT: LinkComponent => (
19+
<>
20+
The page took too long to respond. Try again, and if the issue persists{' '}
21+
<LinkComponent>upgrade</LinkComponent> for extended timeouts.
22+
</>
23+
),
24+
EFATAL: () =>
25+
'We couldn\u2019t process this URL. Make sure it points to a valid, publicly accessible page with HTML content. If the issue persists, try a different URL.',
26+
EPROXYNEEDED: LinkComponent => (
27+
<>
28+
Microlink detected anti-bot protection on this URL.
29+
<br />
30+
Bypassing this restriction requires Microlink's{' '}
31+
<LinkComponent>PRO plan</LinkComponent>.
32+
</>
33+
),
34+
EMPTY_MARKDOWN: LinkComponent => (
35+
<>
36+
The page may not exist, return empty content, or be behind anti-bot /
37+
anti-scraping protection. <LinkComponent>Pro plans</LinkComponent> can
38+
bypass most protections.
39+
</>
40+
)
41+
}
42+
43+
export const ApiErrorTitle = ({ code }) => {
44+
const { title } = getErrorMeta(code)
45+
return <>{title}</>
46+
}
47+
48+
export const ApiErrorBody = ({ code, fallback }) => {
49+
const fragment = FRAGMENTS[code]
50+
if (!fragment) return <>{fallback || 'Something went wrong.'}</>
51+
52+
return <>{fragment(PricingLink)}</>
53+
}

src/helpers/api-error.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const ERROR_META = {
2+
ERATE: { title: 'Rate limit reached', showRetry: false },
3+
ETIMEOUT: { title: 'Request timed out', showRetry: true },
4+
EFATAL: { title: 'Processing failed', showRetry: true },
5+
EPROXYNEEDED: { title: 'Antibot detected', showRetry: false },
6+
EMPTY_MARKDOWN: {
7+
title: 'This URL couldn\u2019t be analyzed',
8+
showRetry: true
9+
}
10+
}
11+
12+
const DEFAULT_META = { title: 'Request failed', showRetry: true }
13+
14+
export const getErrorMeta = code => ERROR_META[code] || DEFAULT_META
15+
16+
export const isRateLimited = code => code === 429 || code === 'ERATE'
17+
18+
export const normalizeApiError = (json, res) => ({
19+
code: json?.code || (res?.status === 429 ? 'ERATE' : undefined),
20+
message:
21+
json?.message || (res ? `Error ${res.status}` : 'Something went wrong.')
22+
})
23+
24+
normalizeApiError.fromMql = (err, fallbackMessage) => ({
25+
code: err.statusCode === 429 ? 'ERATE' : err.code || undefined,
26+
message:
27+
err.description || err.message || fallbackMessage || 'Something went wrong.'
28+
})
29+
30+
normalizeApiError.fromNetwork = err => ({
31+
code: err.code,
32+
message: err.message || 'Something went wrong.'
33+
})

src/pages/markdown.js

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import { FeaturedToolCard } from 'components/patterns/Tools/ToolCards'
4949
import { TOOLS as TOOL_CATALOG } from 'components/patterns/Tools/toolCatalog'
5050

5151
import { useHealthcheck } from 'components/hook/use-healthcheck'
52+
import { ApiErrorBody } from 'components/patterns/ApiError/ApiError'
53+
import { normalizeApiError } from 'helpers/api-error'
5254
import { extractDomain } from 'helpers/extract-domain'
5355
import analyticsData from '../../data/analytics.json'
5456
import ossData from '../../data/oss.json'
@@ -1015,11 +1017,7 @@ const Hero = function Hero ({ onRequestTiming, heroLayout = HERO_LAYOUT }) {
10151017
const elapsedMs = Date.now() - t0
10161018

10171019
if (!res.ok) {
1018-
const message =
1019-
res.status === 429
1020-
? 'Rate limit reached — try again in a moment.'
1021-
: json.message || `Error ${res.status}`
1022-
setError(message)
1020+
setError(normalizeApiError(json, res))
10231021
setIsLoading(false)
10241022
return
10251023
}
@@ -1048,7 +1046,7 @@ const Hero = function Hero ({ onRequestTiming, heroLayout = HERO_LAYOUT }) {
10481046
}
10491047
} catch (err) {
10501048
if (err.name !== 'AbortError') {
1051-
setError(err.message || 'Something went wrong.')
1049+
setError(normalizeApiError.fromNetwork(err))
10521050
}
10531051
setIsLoading(false)
10541052
if (fetchResolverRef.current) {
@@ -1504,7 +1502,10 @@ const Hero = function Hero ({ onRequestTiming, heroLayout = HERO_LAYOUT }) {
15041502
maxWidth: '300px'
15051503
})}
15061504
>
1507-
{error}
1505+
<ApiErrorBody
1506+
code={error.code}
1507+
fallback={error.message}
1508+
/>
15081509
</Text>
15091510
<ErrorDismissButton
15101511
type='button'
@@ -2954,26 +2955,22 @@ const Capabilities = () => {
29542955
)}`,
29552956
{ signal: capHtmlAbortRef.current.signal }
29562957
)
2957-
.then(r => {
2958-
if (r.status === 429) {
2959-
return { error: 'Rate limit reached — try again in a moment.' }
2960-
}
2961-
return r.json()
2962-
})
2963-
.then(json => {
2964-
if (json?.error) return json
2965-
const html = json?.data?.html
2966-
return {
2967-
html: html
2968-
? typeof html === 'string'
2969-
? html
2970-
: JSON.stringify(html)
2971-
: ''
2972-
}
2973-
})
2958+
.then(r =>
2959+
r.json().then(json => {
2960+
if (!r.ok) return { error: normalizeApiError(json, r) }
2961+
const html = json?.data?.html
2962+
return {
2963+
html: html
2964+
? typeof html === 'string'
2965+
? html
2966+
: JSON.stringify(html)
2967+
: ''
2968+
}
2969+
})
2970+
)
29742971
.catch(err => {
29752972
if (err.name === 'AbortError') return { aborted: true }
2976-
return { error: err.message }
2973+
return { error: normalizeApiError.fromNetwork(err) }
29772974
})
29782975

29792976
const mdPromise = window
@@ -2983,30 +2980,27 @@ const Capabilities = () => {
29832980
)}&data.markdown.attr=markdown&meta=true`,
29842981
{ signal: capAbortRef.current.signal }
29852982
)
2986-
.then(r => {
2987-
if (r.status === 429) {
2988-
return { error: 'Rate limit reached — try again in a moment.' }
2989-
}
2990-
return r.json().then(json => {
2991-
if (!r.ok) return { error: json.message || `Error ${r.status}` }
2983+
.then(r =>
2984+
r.json().then(json => {
2985+
if (!r.ok) return { error: normalizeApiError(json, r) }
29922986
const md = json?.data?.markdown
29932987
return {
29942988
md: md ? (typeof md === 'string' ? md : JSON.stringify(md)) : ''
29952989
}
29962990
})
2997-
})
2991+
)
29982992
.catch(err => {
29992993
if (err.name === 'AbortError') return { aborted: true }
3000-
return { error: err.message || 'Something went wrong.' }
2994+
return { error: normalizeApiError.fromNetwork(err) }
30012995
})
30022996

30032997
const [htmlResult, mdResult] = await Promise.all([htmlPromise, mdPromise])
30042998

30052999
if (htmlResult.aborted || mdResult.aborted) return
30063000

3007-
const error = htmlResult.error || mdResult.error
3008-
if (error) {
3009-
setCapError(error)
3001+
const capErr = htmlResult.error || mdResult.error
3002+
if (capErr) {
3003+
setCapError(capErr)
30103004
setCapLoading(false)
30113005
setCapHtmlLoading(false)
30123006
return
@@ -3437,7 +3431,10 @@ const Capabilities = () => {
34373431
maxWidth: '300px'
34383432
})}
34393433
>
3440-
{capError}
3434+
<ApiErrorBody
3435+
code={capError.code}
3436+
fallback={capError.message}
3437+
/>
34413438
</Text>
34423439
<ErrorDismissButton
34433440
type='button'

src/pages/pdf.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ import { FeaturedToolCard } from 'components/patterns/Tools/ToolCards'
4545
import { TOOLS as TOOL_CATALOG } from 'components/patterns/Tools/toolCatalog'
4646

4747
import { useHealthcheck } from 'components/hook/use-healthcheck'
48+
import {
49+
ApiErrorTitle,
50+
ApiErrorBody
51+
} from 'components/patterns/ApiError/ApiError'
52+
import { normalizeApiError } from 'helpers/api-error'
4853
import { extractDomain } from 'helpers/extract-domain'
4954

5055
import analyticsData from '../../data/analytics.json'
@@ -707,11 +712,7 @@ const Hero = function Hero ({ onRequestTiming }) {
707712
const elapsedMs = Date.now() - t0
708713

709714
if (!res.ok) {
710-
const message =
711-
res.status === 429
712-
? 'Rate limit reached \u2014 try again in a moment.'
713-
: json.message || `Error ${res.status}`
714-
setError(message)
715+
setError(normalizeApiError(json, res))
715716
setIsLoading(false)
716717
return
717718
}
@@ -728,7 +729,7 @@ const Hero = function Hero ({ onRequestTiming }) {
728729
}
729730
} catch (err) {
730731
if (err.name !== 'AbortError') {
731-
setError(err.message || 'Something went wrong.')
732+
setError(normalizeApiError.fromNetwork(err))
732733
}
733734
setIsLoading(false)
734735
}
@@ -1208,7 +1209,7 @@ const Hero = function Hero ({ onRequestTiming }) {
12081209
bg: 'gray0'
12091210
}),
12101211
{
1211-
aspectRatio: '4 / 3',
1212+
aspectRatio: '16/10',
12121213
WebkitOverflowScrolling: 'touch',
12131214
scrollbarWidth: 'thin',
12141215
scrollbarColor: `${colors.black20} transparent`,
@@ -1309,13 +1310,13 @@ const Hero = function Hero ({ onRequestTiming }) {
13091310
<Text
13101311
as='span'
13111312
css={theme({
1312-
color: 'white90',
1313+
color: 'red5',
13131314
fontSize: 0,
13141315
fontWeight: 'bold',
13151316
letterSpacing: 0
13161317
})}
13171318
>
1318-
Request failed
1319+
<ApiErrorTitle code={error.code} />
13191320
</Text>
13201321
</Flex>
13211322
<ErrorCloseButton
@@ -1330,14 +1331,16 @@ const Hero = function Hero ({ onRequestTiming }) {
13301331
<Text
13311332
as='p'
13321333
css={theme({
1333-
fontFamily: 'mono',
1334-
color: 'red5',
1335-
fontSize: 0,
1334+
color: 'white90',
1335+
fontSize: 1,
13361336
lineHeight: 2,
13371337
m: 0
13381338
})}
13391339
>
1340-
{error}
1340+
<ApiErrorBody
1341+
code={error.code}
1342+
fallback={error.message}
1343+
/>
13411344
</Text>
13421345
</ErrorModalBody>
13431346
</ErrorModalWindow>

0 commit comments

Comments
 (0)