Skip to content

Commit f71e4e7

Browse files
authored
fix: app resvg crashes (#1590)
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
1 parent a2ee968 commit f71e4e7

File tree

6 files changed

+79
-11
lines changed

6 files changed

+79
-11
lines changed

frontend/app/components/modules/project/views/contributors.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ const tmpClickedItem = ref('');
140140
const { scrollToTarget, scrollToTop } = useScroll();
141141
142142
const onSideNavUpdate = (value: string) => {
143+
// Skip DOM operations during SSR
144+
if (import.meta.server) {
145+
activeItem.value = value;
146+
return;
147+
}
148+
143149
tmpClickedItem.value = value;
144150
if (value === sideNavItems[0]?.key) {
145151
scrollToTop();

frontend/app/components/modules/project/views/overview.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const { data: overviewData, status, error, suspense } = OVERVIEW_API_SERVICE.fet
107107
// delete the search queries from the overview data
108108
const data = computed(() => {
109109
const data = { ...overviewData.value };
110-
if (overviewData.value?.searchQueries.value === 0) {
110+
if (overviewData.value?.searchQueries?.value === 0) {
111111
delete data.searchQueries;
112112
}
113113
return data as HealthScoreResults;

frontend/app/components/modules/widget/components/shared/widget-area.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,12 @@ const sideNavItems = computed(() =>
208208
);
209209
210210
const onSideNavUpdate = (value: string) => {
211+
// Skip DOM operations during SSR
212+
if (import.meta.server) {
213+
activeItem.value = value;
214+
return;
215+
}
216+
211217
tmpClickedItem.value = value;
212218
if (value === sideNavItems.value?.[0]?.key) {
213219
scrollToTop();

frontend/app/components/og-image/leaderboard.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,19 @@ SPDX-License-Identifier: MIT
3333
left: 80px;
3434
top: 136px;
3535
width: 807px;
36+
height: 160px;
3637
font-family: 'Roboto Slab', serif;
3738
font-size: 56px;
3839
font-weight: 300;
3940
color: #0f172a;
4041
line-height: 80px;
42+
overflow: hidden;
43+
display: -webkit-box;
44+
-webkit-line-clamp: 2;
45+
-webkit-box-orient: vertical;
4146
"
4247
>
43-
{{ props.leaderboardTitle }}
48+
{{ safeTitle }}
4449
</div>
4550

4651
<!-- LFX Insights Logo -->
@@ -53,6 +58,8 @@ SPDX-License-Identifier: MIT
5358
</template>
5459

5560
<script setup lang="ts">
61+
import { computed } from 'vue';
62+
5663
const props = withDefaults(
5764
defineProps<{
5865
leaderboardTitle?: string;
@@ -61,4 +68,16 @@ const props = withDefaults(
6168
leaderboardTitle: '',
6269
},
6370
);
71+
72+
// Strip emojis and other problematic unicode characters that can crash resvg
73+
const stripEmojis = (text: string): string => {
74+
return text
75+
.replace(
76+
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{FE00}-\u{FE0F}\u{200D}]/gu,
77+
'',
78+
)
79+
.trim();
80+
};
81+
82+
const safeTitle = computed(() => stripEmojis(props.leaderboardTitle));
6483
</script>

frontend/app/components/og-image/project.vue

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ SPDX-License-Identifier: MIT
1717
left: 80px;
1818
top: 80px;
1919
width: 807px;
20+
height: 70px;
2021
font-family: 'Roboto Slab', serif;
2122
font-size: 56px;
2223
font-weight: 700;
2324
line-height: 1.25;
2425
color: #0f172a;
26+
overflow: hidden;
27+
text-overflow: ellipsis;
28+
white-space: nowrap;
2529
"
2630
>
2731
{{ truncatedProjectName }}
@@ -35,11 +39,15 @@ SPDX-License-Identifier: MIT
3539
left: 80px;
3640
top: 80px;
3741
width: 807px;
42+
height: 60px;
3843
font-family: 'Roboto Slab', serif;
3944
font-size: 48px;
4045
line-height: 1.25;
4146
color: #0f172a;
4247
font-weight: 300;
48+
overflow: hidden;
49+
text-overflow: ellipsis;
50+
white-space: nowrap;
4351
"
4452
>
4553
{{ truncatedProjectName }}
@@ -51,11 +59,15 @@ SPDX-License-Identifier: MIT
5159
left: 80px;
5260
top: 140px;
5361
width: 807px;
62+
height: 60px;
5463
font-family: 'Roboto Slab', serif;
5564
font-size: 48px;
5665
line-height: 1.25;
5766
color: #0f172a;
5867
font-weight: 700;
68+
overflow: hidden;
69+
text-overflow: ellipsis;
70+
white-space: nowrap;
5971
"
6072
>
6173
/ {{ truncatedRepositoryName }}
@@ -67,11 +79,16 @@ SPDX-License-Identifier: MIT
6779
position: absolute;
6880
left: 80px;
6981
width: 807px;
82+
height: 108px;
7083
font-family: 'Inter', sans-serif;
7184
font-size: 24px;
7285
font-weight: 400;
7386
line-height: 36px;
7487
color: #0f172a;
88+
overflow: hidden;
89+
display: -webkit-box;
90+
-webkit-line-clamp: 3;
91+
-webkit-box-orient: vertical;
7592
"
7693
:style="{
7794
top: repositoryName ? '224px' : '174px',
@@ -139,30 +156,43 @@ const props = withDefaults(
139156
},
140157
);
141158
159+
// Strip emojis and other problematic unicode characters that can crash resvg
160+
const stripEmojis = (text: string): string => {
161+
return text
162+
.replace(
163+
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{FE00}-\u{FE0F}\u{200D}]/gu,
164+
'',
165+
)
166+
.trim();
167+
};
168+
142169
// Truncate project name to fit in one line (approximately 30 characters for 56px font)
143170
const truncatedProjectName = computed(() => {
171+
const cleanName = stripEmojis(props.projectName);
144172
const maxLength = props.repositoryName ? 29 : 24;
145-
if (props.projectName.length <= maxLength) {
146-
return props.projectName;
173+
if (cleanName.length <= maxLength) {
174+
return cleanName;
147175
}
148-
return props.projectName.substring(0, maxLength).trim() + '...';
176+
return cleanName.substring(0, maxLength).trim() + '...';
149177
});
150178
151179
// Truncate repository name to fit in one line (approximately 35 characters for 48px font)
152180
const truncatedRepositoryName = computed(() => {
181+
const cleanName = stripEmojis(props.repositoryName);
153182
const maxLength = 29;
154-
if (props.repositoryName.length <= maxLength) {
155-
return props.repositoryName;
183+
if (cleanName.length <= maxLength) {
184+
return cleanName;
156185
}
157-
return props.repositoryName.substring(0, maxLength).trim() + '...';
186+
return cleanName.substring(0, maxLength).trim() + '...';
158187
});
159188
160189
// Truncate description to fit in 3 lines (approximately 150 characters)
161190
const truncatedDescription = computed(() => {
191+
const cleanDescription = stripEmojis(props.projectDescription);
162192
const maxLength = 150;
163-
if (props.projectDescription.length <= maxLength) {
164-
return props.projectDescription;
193+
if (cleanDescription.length <= maxLength) {
194+
return cleanDescription;
165195
}
166-
return props.projectDescription.substring(0, maxLength).trim() + '...';
196+
return cleanDescription.substring(0, maxLength).trim() + '...';
167197
});
168198
</script>

frontend/nuxt.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ export default defineNuxtConfig({
7575
path: '/fonts/RobotoSlab-Bold.ttf',
7676
},
7777
],
78+
// Use compatibility mode to prevent resvg crashes from taking down the app
79+
compatibility: {
80+
runtime: {
81+
resvg: 'node',
82+
satori: 'node',
83+
},
84+
},
7885
},
7986
plugins: ['~/plugins/vue-query.ts', '~/plugins/analytics.ts', '~/plugins/canonical.ts'],
8087
css: ['~/assets/styles/main.scss'],

0 commit comments

Comments
 (0)