From 2129c6a6a4913e956d1ba2d955589c20245918e4 Mon Sep 17 00:00:00 2001 From: Onur Date: Sat, 16 Mar 2024 02:48:33 +0100 Subject: [PATCH] feat: implement optimizations (#75) --- bun.lockb | Bin 261679 -> 262048 bytes package.json | 1 + src/app/[slug]/opengraph-image.js | 15 +- src/app/actions.js | 4 +- src/app/api/revalidate/route.js | 2 +- src/app/bookmarks/[slug]/opengraph-image.js | 8 +- src/app/bookmarks/[slug]/page.js | 8 +- src/app/bookmarks/opengraph-image.js | 15 +- src/app/journey/opengraph-image.js | 16 +- src/app/opengraph-image.js | 8 +- src/app/page.js | 6 +- src/app/workspace/opengraph-image.js | 16 +- src/app/workspace/page.js | 3 +- src/app/writing/[slug]/opengraph-image.js | 13 +- src/app/writing/layout.js | 6 +- src/app/writing/opengraph-image.js | 15 +- src/app/writing/page.js | 6 +- src/components/journey-card.js | 2 +- src/components/submit-bookmark/form.js | 3 +- src/components/writing-link.js | 7 +- src/components/writing/writing-list-layout.js | 7 +- src/lib/contentful.js | 459 ++++++++++-------- src/lib/fonts.js | 24 + src/lib/github.js | 9 - src/lib/raindrop.js | 15 +- src/lib/utils.js | 31 +- 26 files changed, 370 insertions(+), 329 deletions(-) create mode 100644 src/lib/fonts.js delete mode 100644 src/lib/github.js diff --git a/bun.lockb b/bun.lockb index fed69e6db1cc76b82984a4cc871cd4396568ea7e..bab61ea674044a46981c9876e03144069b2bbb53 100755 GIT binary patch delta 47096 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 Xd5uq!5|aGxsJNPSsroF3%~<;{l_dJY delta 47338 zcmeFadz?*W|Np=DVl$iMe3*uu5`)2D%xIX65Hf_6GlO9U<1pha%_QVlX{n1GqKLsH zAyks2NYXhfoexGyI^2<4e$UrhYY%nnzVFZD@%eoJ_&wSWul>H>uj_rC-`8QSS!-K$ zvE=4|mb||~^gnmc7;|vbj3t}zJUQr{>o2Tad$Hc8{TJI@bs)QquXK%sryjXb$fwVg zMYmVA%4aT`+$j4U%jdhw=gS#IOc41gy5uLARX~mi_g^RfbRrC9NDspUkX3F@~EMLY{8p!9N(q5a>Qb(r`o8r6E)8`_|HRo~C5zHArYK*$X zmrnj_V@CSu^sy;fSudfhFWY-^Dut9pKY~=>wRHLHoHTk)o!A$tjBll3N?037x8z*( zxyuUBw;5Wk?Ouwb#1*Z1nQk z5Sgi2V@6F(^<`G|`S_QU#+O>q2dS<~OUW8DX|&I`shV3*X)ojM46)*eWM!ph4e|L3 z!Al=ICTl#LuRlYtioH%PmEY);(PPqw`g}j2)5e@BDH)@(X^8Jp2-Wy&&+yvo+=NHb zmEp_i%3!P~xVYrFaNMk=0i?IwlG zM|Y^>#v3ocdy$IINX;Cc+Sum{^A+rIg)2tCAMI9uNnO_=zC@BBrwBYfkTZ1DL{-u^ z)810JYW6kt-7-Hy%3o7PPsvCbKO`+BbGTzQo}@};F(uU2vrIwNHEYc1(W#j}-&g4B ztPhZCYt|%4KC>ITqc{yIekijiX4vSgA)~S;D?R}pceQU1L}mw@xYPFs+M?R{!R zwa)}(>3z5lP%n8-fZ7hq;}~sNv<}awcE(<_O2qK?4E7hCY-*} zbCEG)Mh#BMN*y{TW5W3KQCVNXtBFsxb%*0oPo|C@pFTc4H7h*1I*^abY)qm^qw`c>{FzqnQv4B8 zYy3W>3S5n3e&x(XY79prnbIEEBIWE88iJt3lI zcKoewQ_uEr8}er^-T zAj_Z+Kx%HUCLi_nJ;-a2lkB^KRkM!_a7SbYP0+}U%}gDSD`ieXS2H%GxXoXUlsjjo zW=zz~Z$H>=-Uyb0IBd1EWu#|~e;Z!u2B!~iFoLdWhOQ1DpE5RO@TgRu#b7F*X~>d3 zU%j031k^u2AeHbVWI1GdR!U}O$`oJv=(N<#^zm7547EEIyEeKZ3zN)Q_G5;PN*Nx7 zF7xpIRIt(6i;7i@4%a@D4M3m1BizoXvW|WWx(Z!yzgete>=z^5x+actz56Y6_1qWe zYQuaIsjLFsjx1i)|B~Ijc*CH7oZGHm_PF8|vol7!n^jYKN`u`KU1K(l9F$WAQmq(H z52^ftp56ti5|hWfV>)4UI{Es1+tB5Nw0QjZ)Y0s+F%vSWRVy!D6Qt(j^{%`oC(`@;5*fr`l3o3(ils9cU1b+uFCMWwURAqe#uT?Qy^(4r+rTJ#!1w1aznOSdK7^Eq z$03zlxjS6Gy1nbFs?pv6|I7Feo-k~f94I|CH8no2sqY5cU!r1mxND11Z`?v|I3#`a zknvlx-8>)8apgIr+Hnf0vKJ!N@EJ&Tb{UHYGDF^-Xw<-^IQHRw zKHr1~Tszl`hx%4xuL`GTjhm2~*<^|@HH&elyTj9FjqO$>WyqLJb-@E9P|FT2cKIl~ zcgfn>pFHUL>wcsbwrN<&2l^wgMYcg|X$udXZ;89GCcqbiuY**z!;3@ZrEV$vJoz+I zKGyAFx73o@tKZMeb@I>7nMgoHh2?I-kw|s$tIOPk?<3XGz7=i;gNdiZbJoI3{sOO6 zV#G>!l^8x|EQ_GecN@AUVgP$N^O{Fo{WR&c(m#PLtzGl+Rc-;FL#U>EkSg$|$J~UE zA=S~t#$?8%rVL4|_NW{G*lIVU1TVhqT6earL8@Ziu#;Sjt{%D@sftYX;vf~&66%^hCGSStr zG^Bn1=@MC2~z26&87d<;!0k`qffeHRAh@graR}j1^$B67?*y^O*jUr798{R z@mt+4eh;Y@zUE1KBrBEGADNLd*5?}tuO91wRKw!8a)wh^{YHTzhK?CBJ~MrE>TwEG zi(W>GzXgYqGtJ-OR;cJsw*upknkAF%y=5u}`xC4FXk~v^rfPPj=e!k$v}Iw#jR-q( z*kLC>?=~vjt6~2OyG7m}@AQUW2FWMv9%29cvzvIso9&FRhIj04H-lbCHR{;2Zslk1 zbBEMas0>XVHz8$|&zCYeHHI}L^>&X>Myi*0l23JHgO}ZzI~A$&9)XYa zb!MX;?QZ1Z4Sarv&vzXLZ4S5zu6e~h$WA7KdU+616`{kNmFl~LuHSujaBHV>4;9{e zqEb#te@#2RPRQTg&aD#)F138VL=u#-x7?gi7^R7Qs!pQ6vYj3s3Qj2G^W8vXVf#!d z-oM$-j}G}y+R=4G{-t(0-#6R2d~dV!`Tp9Dt{3vxwbS{|v~&4>&(7z&lpS3^tse?}9O3hguw&{c2GSUe0SftFw$o!m{)%=k-^1+um{4F7{d1cWXW8iuLV@lq zC|#UXdkGD4VyfHG4MV|^EXi%CrQaSmB%v_Mt#(Yq#DJgq;$#rGoe&A?CI*fX8f<4Y zNDL%0JZbya zzuL}i9tw88hE9j`IZeJFO{EpFw~+dEv<_(WC}r1=DyRu@BRs9BJ+47~@J+PFSXxdW z6slaH(SkU%_HJA#J}?KZjnmFo390Ly+yhn>T4852N%Xg{b6bS`N9=sQH#jZ*oRE?+hIm1}>k7uOT|xv(JEBTD!}SJQ zLH@z3YLOLj3^v{00*wV*#ng!pEI>gx-NsT&{cfhM=GG#!Eq(bNfr zoB=xISu%J_5+Z854G!368aj$nx$e81C^rwB$^V(1-!2p^Q^%cVg`I)#j;0z@3U#~} z&2=BT!vC6`-#!$)KH4prr64vT0!0R{2QIO5Z_?_Oe^V$>r7qcOk?{Ah(>sKMYtZGZ zey6!Vp|!X3>L&)9*Yo*8PJyxb@MJr^V<`AIoZD?%n#Tt}M|1sZOXK)po%*h3*;`t~ z2M2jtAzN`v&@|L^FkN&CO{)Yx%;b-c3Fk7dV|;K7S_9(fBj)%DwAQZ1P<)Km*3PS! z7_5Pb>fuxF{sDG+=TP7QIMzsFex+GaYHL_23N%Bvzqr2j?u|6OFa;~;j+a^tXz8+yMFdMC>owvV5 z1cBSbbg(h*P+%0Ch1RVg?me_imN9W;P!Kl*?Pf)lI1v{^CVqgNHR_Yf2c00XCDEI?9d3g<}#rU;H!#`u=wNl?2 z3cd}eCB|=SkV`iYH&{d02F-1Yv7wJl=Y*rM;n7QlK~k-oTD&f_D?56LX3FY)BGsncx0tE zZnlnZ1qXT>`%K;Vz

5@=xoPHulLrNkLUj6ND9xx+ZvMRtLM?Y@n=u?4UK=AU8GBD7uHDUpJC_rXXk`d&MY={ z5+YEPvAYq~XZZV|xf|DPG`AzRJeg1!#a;S3aL!G#^ZF+SXA@FcbZgys>(!g=4Fi)n zF60jk1@qX!lrK}BsINNMBL*e;>)5%2LcysW!`W(GejSaj@>c0mECquKDlitUdx7>M znp-)IL}ffbOHKGYUZb-PYkJ&L9ga=qqbv`gu3V0XFH#sz3zTwFo~ z%B4oNV$NL3axofR>}B~8n%pwzY;~2%PnOJM76g22cqq6IPI>w4aV)_X(cGm??$nUE zsVPVt_OtEWv{2w?GH7pS)J^m+vZK>k>d@5$mSR#gVv^F4jIR3a4m9OP`UdfVO=unL zjG>AC;&%FokpC7tm+$-R{1GAR-P`QCBa{3U?DUbLU=PaF^v0*-;{7Y_e7G;*F6{;} z)Y#KFVHEF8-{{EdA8zN53I*1pyT{t|ggV(79TS63;2`pCPAT*siiQNGO+w+m&W^wd zVJ0Ce*dWn*t*^adbdtZOoj*Dhyo-L<7RNSHFFx=BT1#imyg=v{9kPST{oSErEu57Q zfx<$e%o7|%qroONEFl7KYm36U313OK^T&pQMF;wv6|j`U^|8~(g;>IJ$AyAJQVMpS z#_`q*DR$k=q~Jdx)eMf4oGAtlcK33YCssb&&d=o10j{ymPk~ZHm}z$2O^JaHgq$fF zoK2`H>Dlm`#|NH4>!1l6C_Yr%y*jEjA(cRD`^Wp|+4S1k6CzNQv2zj( zZb^5mhBL;-Ti>MHBc>z;t{dU=)mI+{M-!4yxb1ijt$~xznepsdXe!Y;NCfMQ43B{h zXebG8GAi@1r@5#7pU^rG=UgiU`m-pturoNeE+y0n9zTeV5B!AI!8tG{X1HC+YT#T; z=H3yqe$B8?-jNhsFxuyfcaofG7a#0A#^-B-Mk`rFve7iAMV&*`Ry2*DGD(O)(Qr6d zr@@+I-A$2g5Jw(@hNEy$eUOmr6u43FAevg_PT^wX-0a*O+M=l==p-y>pt=2vKL@v= z)pz1p*aM%U-D;n@BQbbWWVs(Q#Ronlrk9Jg+e7#u{XDYiH!wOEYiTx0}~5|aCH z9_1Kw%|usYWH`6=E6`|B@EJ5Yh+C5;liYrxC;D*xb^^lYwl;WSR}Wo6MRuD@|qi{+vPjl(w$c!E%ZbFKb0p~eaYD@`rvAO_(! zv|GrB11j3%XpPaFjW>7{tu2~1bT-Cm?!a=TFe)JeMN!T~^)Iob=Z1nu;Z#lc^ibmt z*TU6?p}CVTHa0%E22E{smw*q@xqPF4p9DL-<+ zs>q#T58QG`eDFpz<&Jx^tZ|}V5b|%e(-&}0ftW_bFo&b#gHhRT(`g>lU?iGKir`?8 z5P_m9ML1VmzoMzJ4BaDKqAm;tth?xaIX{=T>Gy_$6Vc@c&fo@iqPgqkKM36hk885K z-8RE@Kc+Vh|2&%9&vnLXce|6F?qZExjCKQYPWJ|0M{DY=b=FLukE!gIV@;iDZ-`3@ zZi7^-n85h)MYImi;NLV$9-R@L7??z;sTTF%MncNiJ3h^_M?8=eY&P2+IOifIn2Dyg zvjMTAZ$MK=yEQq1#$rQ8jqdUJIAJjWXBNf>rl2)*E}AwIY7g&Rc3HpNW7n;p6l^%h zE!DXXp`B}S$a-$BUH8GHV3YabvgKoA3N)?8C(yJ2;uzGV(Sm}(!-WQ+wII$N%H^Ks z47qi5fqn9!q+pGOt{-5M5FhA;#=#>xF}RYD4mETma&@IHIP(xhPF``ujK1I{gI>P~mi z@AWii#SXrPrsS?=;YDsbcmA|PQ#xmw2JS{<7*bnA5Ku4Rcl2KA2i$4kc%i?Y9lbmh z%z;xvvZ)_$J^z5cVR=&UXGjf_yH4F;yCuA%tPzqT6T^Po=21S zP{a1|{$6(U%206rVz)Q(K32eY&}8ARzE?l!&M>F7fdOdFX6%2+PJbj6{0Utpv7j)Z zwI2%i_c&(sK(t$kql<~#jMfj$Ss?<&4Hs;7UUG{F0zIMe)S)b*t3$!cOWfjd@6pUR zv~Er^Cb+e3iQ{k|L2C3_S`)aRu(Y6kOuLC_igPF75l?e#U-Dt5xZ@{%2&s+E$oLoA zxsQc{$Kl+`%*w*O*yuH(K#k?(<{UYu5NbyXdN_%RkJiS~&ajsRS1>@%sqv;2nk<34 zAZ~CLxn~t}4CVh3E8VI(J|38X*2|gZ9}sdku40eSX}TE}>_bRL1Uif*VW-P;R*G2V zp5Qs{^k9QRla-TWa0{9|#W|$~PNUJ>*u-G$YWMt#)j$n;{`!#h)MNI^n54kj$26jO zEfW2W?A+W?aP*pR_Egw4Z9|_JOU0 z9J}BtLT;V5aL;xf7YEK3Hi?kipJ(un=g_psI!jcr%=*i;KpGlp*b281>Zb+;O659T zM<|U@cjt)v3?WtrZd>~{s7)COiGc({ot%3;GYB*IobdxhgMIx7hkkInVXwWaUt}Cz2T{(V42OX=aiDe`8(UuTSI|+;o3N7 z^fw5(*|8Z1DnIEIvo+B_$d29?3OoJKDtRaUpM&-#4HXwLn;$iC0{eB;pqzCV82*sY(d?GM`N&xQQP z&V4Qv_${ld0^nlCuh zjCs(Xkkhe&t%MjxxT1T-Y3B4N)Y<8zTtaRqT_EIoxGv4xy{Nf`8w6Jq>gAR|JI|r1 z)$DI{p1m);?u}azA2@~9+dkDIG1&g4f`yGmdoh~pW}M!FN70&+gL@FZYQH=!P z=CA7n8493;{ed#3!H$kEI~7Uw_+dzWBxO%0JMkIFA|MN>B2$41oDRg_4P<{0hyeEi z#Xq3(ixF53WUvaT0;_=x)_D3_WCg&~aSFh-oX_P__WOa-zwAi{&-q+Q28el0?YF?| zj@cijGCBez-|*rkW&f6^OR9xOfhzDJko`wMA4&0_Na1t2lwJ0xd@1l5&__}Uz69dG z0{TdbKOu$Bm82?m(usAY>`wvZ^S#GQN_d-`QE+ljb9gfete{e_f^6jrW(lFGP<$4kmi4_=BY?(u&unL^5- zgl8z}w_{$dSQ^q-%3~dAw|upt9{BZ@;Yl#mw63ItWd=-zs*5m)3 z)I_=7vy)V?wx9h)fjVA6c4U^*mlub}>j%4fvAZHbtt}`OAjGCn(ZRK2$YZ z)-61?C9)iPH^u%hN#);z{8ajFUcQ$}i!nHLsJ7GFeTbGtU%ph0F`oJVg_P@LdF74w zd+Q?srvei^Ink?tr0PEfc`ec))vl!;zZ|Lc@G&I+d~5hp{NqyoB*m}ycuBceo}alX z?g=kKQUz{7ih9c9CB<*|^eagfu*0)^2C0@l@7Z52#lPs;?RWYgV!vm2xm1A%;1zX{ zFBNdelZTOtf5X$?^5i?7e9x0dJ*hVg_~-l3(~l!nk+{#ifL=w=M^XiSgH#uNhg3nQ zJ^3TjZzsQAwYu1Ip4IPOte>O=eU`GYXT4rEMU$YI#}@bGRY)0Jt*<{xRjP~^Uk<6< z%6qbcXMef$+a-=v%)SPzCd8_==AQKpNY$|=vLv#z$0s9IN;gmTL@Hfhr0fSE`R5y? zFHfo#DPH_gWDq?ADSott7b}DD9x=f)oQ%{*@@nL4PnT3db3I*B>F@J&NyR^aQ~|cf zUrEaDA?rrIoq${OB_zcf~=*1(m^^l% zNiL`#l#!oU5rvS_i^}?cDCMZ7NT&iyd$Np|kEHT1=joEtD-`lp#fo0Um83GP`_NUh7FM8H%qV(vvFiI!`Z<@AqsO;JDpg0MOp^Ih{4JjB z;mO-P+1r!-km|nyNdEZ->&uf04lC?=$Z#}OAk8z7RKgJ+KN6_|MLaQA78J%qWO%PfEb`=HB>#L%Jh>dHf>t2a6OSTwCfVZY zPb0O3?M7aOJc#6?;N+SIBtfRHFmF+P!Ssv%B4DvKm{Lh{|r$|1Ms?Kko zJn!+6YS-^br7Ogj>z_ zI8q-;1xI;&CQ|9MJbp4#>8E-89Y}p7Rrj1gO*eyC5Lzc6@(hh<=tz6_v8whv$4X^u zGCt~AJmy(Qs!F*?b@@h*my}OzMXE~My!fZRcuDa)kQ&10Jw7a{=VmQ`MoBs6wJSf_Vs^ z`WUy2!FY$68v`*X2I7>6U8Z>h44*NJB%U?-2y>zV(a8;o-ffmPgjmuL;(~}hreh z*eRk)Q;0(*y(vUmQ;5SN4x7k0h)Qt~)8inHn1dn?h=^$h@ur#53}SLKh~pyOHas(p zAH+i}jE8vF920R=M9Tz-_s!e{h&c%mr$ihx&6`6cG>2Hx9O6ThFXDuV8F-vZ(bliLDfeG7;ZEg`-#y<0-`Y6-Dh#5X3G z1QC%0k&y(EZ+3~;DWVEL*Wv%xq=z8VLJ)^VoHCKEAS$(jnBEHFv^gl^fQXpZ5NFJk z))13hLmU_JlZkEvQKt>W!Zr}Um}4T2ifDNw#II)VjSzEggg7PQoN3+`BB3qBinb8v zO}>Z|B9hxdTrf-9K`dzpaRI`*=rf(#`)@NH+Qa0v$HZ^Wwa4VFi2gT06f(ItL9D+C zqC^LXfa%=;}=H8$@0=i0bB?h_fR4 zcZaBHa=SyU?+#I-2ShE?y9Y$C9uT`l)HcCeAtG*t$hZ|E+UydsQ$&@X5cN!YPl&Xh z5Qjy?n8@28D%}P#{Wge(=AeiJB4Tcbh&5AghnRdj#BmW#Omr`ZI=vtk_JW8r$3z?z z(Xuy0yqVh@Voq;}QzDw1=6xU%`arDc1CeO*MVt_k+!vyyS=tw3NneNyB0{EPKZp+f zAoBV_v^M8NoE6c(Kg5kDw?D-C{tzVwK(sTx2SD^10I^%dO(r-HB4Qv!#z2UUW|xSa zBB~66xY?u+f=C+#aacsMiA;g0lman51){4tDB^&Kn86U;%#^_plLtc_7tzB+4}qvN z1Y+S3h@R${h@&D}4u!bg%pJMD#YzQy~&kAy%Y9^fmb+PKZbz2GQRv9R{&v z7{mn;15L-_5FLg?NG3P{_712KpVu;C2gIJ#iQ6e28)$~q>=#>t!Tf}e^903tA z0wQAsM7r4}VyB2IBOykb^pOy0BOwlp$S{$kAS#W5m_7<(j5#RcfQXn3h;e302E^nH zh~px%O!R1oI-?;Lj)s_Ej)^!bqU9KfNoMXCh&f{*PKlUenvaD@7z?ptEW|XEFXDuV zNcxCq-sPl2d21!Cb8hzHFv5l2O|oC;yg+^Gm#9Grk8=_Y>#BLGmOfUx`A_pQP2O`((60uW6mAfD|n)JIM((ZydEF#ZD&VZ;i z17i9Nh|T7phyx;G?uOW6rrZrN`EH2gBDR|7nGkhmLM)sKvE3XKaa2UhSr9wS+*uHF zWK%6!QMH~2iR=HsRQ zPC9@*3{mG{EEYbD#V_WVh@&D}E`#{h%v}aCXBosP5$8chg-q@v5bGa-D6tA6V0y2D z=(P%Bw}_%9xEdm2HAKd0h@jaeVyB2Ik3tkT>5oFBJqmGHLQPxDSg{ZR@V&Phd^5&R`qas>94pGs}eH>!W;}EArM4IO7 zAQIL=tXKyTW%5Ow5RtqdqKa9%9%9LQhzlaBnvS^;9daS^av`dlb0W@)=)VD?rpes^ zv3>(YiH#7oOz({ly*5JZ7E#*-H$g;fg2>ne5p8yf*eRk)9z;Epo(GYZ2XRO2Xt@JWa`b48Fza8R6le-;a{dR~FPeZgby`P5Y^)$q85jUCO4v2^y5E(llI+|S~c8aL76XIr* zz7ryCC&Xb9$tH3aM5SF2(|191H3vl;5E1hXL^m_#8HmZxKpYp*!$dy|QRi8Rh0j9t zG{;07718oJh}+HF=lGiQ9KR_uo8Yw|^$5Rv>mM1QmNd59&?LtGFs z&~)4b(P0ln-X4e)b56ur5&d6)7-Di?fLQ+mM2Wo+siyZ{h+cakc8eHpf-gcuyabp;FR}KgnFgQxudUu;l=~~7Waxa9bNJV)3(`z;s(;5P)YK_v z$EE7m{}M8jx3Nm9>?0+@KNnq)w&ITZUr}CM`1hEd2p&^XLE+zKzMKpHPIEy9Ar)SI zVUF{hYtH4SGvIgz%jF81UZV31cO(mP7{24X6aGPd4lKzh{ipr6>}dI|{}aEz>y9d? z{5^{JEA3#Yzp&gcQm5A}M3E6ic={}7#zE8Ib^icoRCauK&i~8g;=M9eb)WB_lPy!a zaiOxNQI-|m?)QuCM=|v<(6cnI-5+)Dktkn%A{DRueR|ePp9W@imQ}W8Loc~Z^(2`- zjXX}Kr5uKL^E^(ETIpFKeHxoHWRa~G08|6rX{#)rPg6MlIWNKC%+9Bo$LUG7JG}ez z30_(~e5d~qMIXILptO3@_$oN%o#=6Tp8qqRJX3s2zKC<5fcsL9qwz^{NvGc72meDJ zrw7#TQA$3okP7N?y>%k^wDIipXx#>f;aN(L)B8EwJg%+Bl}CS?@|{mRk5uRIUaa$J z?{O8;t|6??O&(W?aAl9{;Bk>~RXwhw$LSS|>pZTL$FX`i-9oU_#wJpUfQZ~YrXF84X13X0X>+gq0u`UDx*3m zmh;H)OB^-fD#B?D2Vtn_>wunsl$)h`8P+1ak+43)kaCOb!8VUe_c&HO-%gJkg;cN( zct#KH>%-e1P7R|8>%|U@^%$fwtPAFOoaU2SQV+;4^vUwL`h+!%`h;I9i6N|E)JHFv zC~X6vVbo`$MfoBdf-Rm`Px1^K!5swZ^2r{@GVObk?ogLc@wmo>w|LxCk81)q15RB& z4Nk9ty8%4rajQHo5pFSIr~j2ehAqIu3h;T<kwO7G)aQxHn ziTT=kT&|b46^4!>9NgvYgm z)5|UDjm=1v*B-1TtloIav%86~A*_~fMe>jL#cBU)uf^Lv!;VC}1@w8^<2n(38>lyS zc!_T&{Eo-%^6WaJzX$ZeyPZOk3BM1N_BoI1Lb$4Gs{Vi8OWc*Px<)P9<8ikTR?mog z!Q;9S*2{Yxkb6C@JK=ADDzA5g)TSQb3!qBv^SE0H9|Nk?OK?}qu#Q}rPm7tR(=6+H zQ-7+p%GwbyuTQlqSd$BxuculMW$Q8KBA_VHVjl#>K?$Hor#}XtfKP$e^RK|yARn9r z--7SJDUeRnM}kox1B?OVKo%GeCIYShTJPt83;I3Gi_~x}cpR()>p?EiVxI?|0Gq*+ zU<=TK&(AQh$TMbOC(zoig`hEan(wgc#>kW9xvhZK_{yLv(3)Kb)CJn3>x05(^&M8l zS{A`c^qL-p)4QHyfnG&T0wK^E+-Q#9Vb#vos-h*R5mYSL16}}o!6BOXI?&&^^@46B zc%JYc@B%0Y%7c@n{}y})PEfSo=g>-hn_&$3o8f(#@Ag9TtASOm0lXrs{6_$$F9U=>&m{O}eyOa6L% zUauB)EOa}+h(X|Hphx%Dfem0I*aY&x6JRrV5$9!QY`u%C7j^Z{u-;479ukMttE+ke<_%Dp z@HJpB;ZKOKfczeL3T!6)B+y>85u|{@;1%KyfLFmG@C2Rx6c|R_qsS%Tp#XbjK7?Li zxd`;aN?{NH5g-VPgA$;uX`N%0EfGoZ8W07lnUoxyHj&`3U>itLn4IbF&H|taI6?+LgI~ZufOe}W_{yLP&`zaC zy0nLDo&Ax*_k#iG13`K49Qb)DCK%XU{4v9JxR>QU&hyXtD93lgLC`bXi%D4gO zDn~Es4+4Wt>fKh&?1PxS zl}8II+eUBc-9+F$3fGxKXAGU9-Ujaiou1YJotRe8X*&Gqu=6g^VWul-y8%70*BP9| zP7OH@BGE6;_i?y~!3v;ri_R$@wdU(%@Fn;Rd;xSY`2bYEEJOV|foz12K-x@l;WKEN z&MGysC!%h}rVBFMy*Copj}PbvDpIi3mf&m+gliDiRuMs1-lILUB4KS0@}S=l95;OX zzzbkE(1}7P3!N~Q1H3>hpl_KDcn|ORWtJ6RxXMz9B|v+~Vh}cZi134^<2;puW7ecC6q?#zc33ISW*00WXl4p zo>{fPiZCB7u&(M@5RZ-W{UL1c>3=R;ouK}dt>S+6!pBYQLMy6#IJ2%(^j@oC zup+?Kyp+D+;Joh%Z8HWmF0%~(vMk>c5HoS&Z2&(PEdnp8Tb>$u(=GzR+VnRw6) z=pfe=Gywy!Rrm%F0!g5Sewn2ufmWb3&|a=>@kuZM*kCLe14e@kFdPg6L%|U60JtAa z0h7Vapd)Ap+JYNF2XGTmrQ3r}K$j!~^vf*&seoHB>;gK2WYEdxC6*eaUu8| zmcLx#~hiI3&aqgd1D#&Dqw-RzPPl4O2s;GHQT~0lLQ4wRSYnwYR3} zX|NT14^Dw^z}Mgl@Hx1XGCo6o1XOnR%@A^$55WiED0mmV1KtMmjkmyS;AJos`Y?|+dq53P_!tl`gd6fH;ZMME@UiCqR|M1pUjmJbx<xxB(^WmOOW*Mdr*3MdA2!LOS{YOM;5BCH*~62TzJf7_g7)rt5?E>OR86Y()jB>S~#(4s&0}Vh76UlbY&zxY%gci zSgR+l18S{WCmTr(uZH$=`*0o_vY^IEkyXufV^z%7{kzL;|EfV@UTak&pkdWoba~oX zbd`TOr+UdPLVvPVop8DA0>_uH$x*|8CLe1G|?b;T~03nW^vzS14GmzPvKh)k`-6^;8?s z0ki_0z)e8C)CWwad}oc(^4=CW%R7ZU1h#^9aF=JUmR_#6CtlsF0y=t98Q%^rw^KS* z>_(t`Cm_dz?9qIwRwIET2ZLKcR}emDN~%REK&=@BS_8GH8<b`u}E8`@@>nAKQMQ54atK*ECg3%Zk=BHB5Q-%H}Iv+i>f( z1%zAjFA1=}Qbyv`Vr8nZpjo5d)wo}-hkc_-wR#m zjV7Qc;Y@65UuL5}A?W(?SumNnNkEsd;Y-l_mqGCPs8JzD7HiSBakKst5|XMlZdNVJ z%v)|%*s*50HLp-y)|k^ia? z$D?wHIp+M^h+NZ6+J4jU9TI;^VlGH?e!D*HmvK|ubEOBhevvCzD^-m3TK z4*16G#>VUtp4osM?2%#VP1W$Rs9O_Za!Xo)JsL( zfw*?*N1sl9wtT)*gGL(E!>0dQEAn45DEH@zI3rrl>{)A74Y%r7O;iQ1=)I$-su0y=E^Bu)_lE=e*d=v^~bzdnTt5lf0g3O)o^_1zstZY z>&p3fZS}JA`s}}H$Q85u&r|A;1?@1UH~p90@Nb9n4`ag=>6?c$N0xM__jofs&r0^s zG>7t7Bkna9iR8)l$S17is7JBUQZc!5`Wv6k8So<>+PDD|nM+M`*Av)nH_H%F`>^AF zXwJOM5xe_$%6}QVCae|My>5;?!DK#)!POWv8gpdnqKebwKqQtmj5z+^9hlP2Cmd z?aj2}aZ~h3s=os}?v&-6>^5ZDE%WQ#>Da|KXwF5c8S^BW&Nbu86e?H5S=Amgk3DJC z3w~1Coz$L1j5#C2mZta?M0-{x$^CykXpU^Liu2^>hg2yljZKth=B%Ic`x=k_nVlljDnLx zg~IGaQ%s7817;c`YA0DLVV8z4J{tMzzH%gN=9c-h+3Y4%gQKocG+U{&v+)Qq`OPvV zVhX7onfoi4)4y633ggVxP3kthYrR>z4ey#(+3ka3`L|aIty=IK#d%IY+xWMWaG7BX zkBoYnEra{EIaAJTKl)+lIoAuD$P2GET``DThCvht=SruJ{JrVnrDUXSKenl_PgQql z9vd-n!j#|pRw0J@M=8EEvqD8=nwDh>MVdFZv%YRJu@>RNPZRELsyOM_P0by&h?h%`Segek@?|q_hjmv}C-z=v><4{!@wfX^{^1ne zM4hzPl-NnLiq>}PR=8K=TMrl6upa|&0DGI(7_h_i-AUd>%zZMi5be6*p8Cr^NbFgu zlheq?I@=htPj=3VQo)qiWmVzDiF&)Nnmm%SfSun(9j-CupCRwfIr`z-Bxxb{?PNcXC& z*`~^KR=vOq46ZeM_gLi%&J|wnJU0=`)8bp6qeiX>zCb$7$g(^J4x3<#?q*euX2sJa zYx2O5se4zfnr`_k!)RW{nUohWXoadtW6S{ncce? zpYfQ=OE+g{pC}VNy`-=|RudM3h2{(f{&lAK^HyZ#=dshd?fxIuzt(=@Yc(&~z1P^Z zdfuuO^@Eqw@P!lJUa%-F=aNOxWbd^qn7Pke-FQpkL$c$Av0t9&*w)Kb-(yw(laHpG zNqekzJX*ecj}_VS=>+%iH9B?jc%7r~I{5RkQ1<~VO8Em!C3Qnt3YN=0vHqufx<9<- zAPbqd=KL@9S7h8EiqI-|SM0mv%hj`cd2M6b{6+r}&P zEXjGsIA_eb4$qXzsnHDIjCcLwbdssI&#GjdPck{LGnNq{ch_$Lu8n(a`}x7eJOli1rGa&>vn8Lu6jRa6e~EfEYQ;@P41Pa&pwrv$N2OlM{1&qZgSe3x zT#Lc+)&2VQ+t}^hO9pd^QQq~JckHyb)jgXo#jG|Z_S2+&7-&kxF4(l?$0@BET`K$| zVpQP<)7J-n?G-3=De0M3rr&-#zC>$xh+cSS)tOmc4&8XkqNcfz%;TD4pv=c_c`3Pu zJv`hpI=(wGnhy0Bq%3H*>-bxjiXFgv7OaNOuHY^Dk!HZlR@0~sZQOOEQ1JMiJ1)Mx z(eg9M+6_2S<-N=>_B5}&%zp8@Y4{56)Zc7*#fmf!y~0-Ggp;kWZhYu~)z~_HqiKqa zdbF*(JKS@y*FS64Z1EG7(ArIoPnx?9a7pIam)8R168_8iO2#@!*3P!n#QLDUx%UuG z`E`3!?G3KtW)dfI)j`JoU)Ml3Pp6=UH@Q{Ve}D1&Lbsi`nkr~Hz$;psQwJIQe@$Av zgWC<2O!Zf3bbXU3(cJWWm2Pu4(5Ud+8)KFnDS79d&Zd-q4XL#5IMt2x=1ybt@oNQ5 z_)|+f^}i5qChmZR`@v0ishq`!!{roke=0BBbMDCh$tu#n#2kLzD(Ggl>wC(qe#5$}@{t~{^Nu?5{PtVknsLY({8(+=U-d9aZ!(l;dzcrH z{;SNmH|gOTW_zuy5A>Hg`Wv%X;GCZzVUTmnyi*m`RMb>qQOw zEWCbu%jTA!6Nd9Sc$@k7txHu7ml>AftKvy!;M+{gzrJa5PcL@`{AONMKmWk%%e(h8 z@c%#EPFZa}Bcm(cRf+rbFYl^6L+%`Zb2ib^xwR=6j=fat8ZFO?^j+m;%F4O;b z^Wn+r`AW#>UCq4z<_rg$8i>czNY&7^yVM$SpD%T+q(fU*6hYIY7SXdBCCJy zT$KA>t&z(vHGGvhqpbd$tADR~|Mu2`m)#W`htGv1V zfw!J@eBWvN7?bh=6?TMkx#U&r%0*tGi<s`B7zt}kU)(tZ2#}~tk!7GEz4^+bXe2^*jG27kQK_&^wOZ($KCjAC8|6^90W@Zms zRQtj1Mm*`u!R_i~F48Wqt%(iDyVePQ!nMu@Q~MKE-?s;wexGn)b58E%ia7g}W%hl- zZadAC{*=Y;jiILgr&i}?9}RU^$wP_l6V0@>T`m8e-q!j9F}JLUfo?NA8qnpmq*r~i^oD$VqX6+)O`La8}}71qPepoizw6ZGuqhDbo`7q zPBGJ^9WX}P3Uf%B-+YBu@OI#x9~axascieYG(5rGlfy3*&Uty();q>N*pV1rGn4!E zCi!#vtVz1N1Mhj@%Tq;`L~-9(SG+nmv@&;L;O}CVNenbqen4cHBhsdsZqnwPkqA@y z3#%!=tI+KWDz@ItlGtF*oZ-sj$QN|@E#}l0^lD#|_@z_xy+3kAH|?}l`C6*+pDM^p z%tubUcLV-&g(}adHM(3_e!9}9JtltLht}x$Ol#g6Y3}_BNBUxLJ%Uy~2^5r!2hmP6H#OdX9EiuSFkr^gs3H zpnEJoe?HLt0$+w%^tH7;YD0><13pqKr+&Rzse?=T_mN~Sb$rRBd_#ZqG7n1JYYu+H zdCzZRPjDUi*V#5T*(a#r@GRH!=12ehYxOfNOH)A#R=HGAEitd1u&((p%&y6|y6`S_ zdOmg7W>(}gP{Yl)`RW=sqf7FST@~)SsFf4l)7Z}MYaOu9M5H@?<@EVFv-+f)k-9vb zKpFm^zRoa@T)=y3e2e!?GPi%r+8Q^-?JeJo=hq)-pXuJ$bi6TS_I%5Be=`QUceZi< z>e(|&{5q7vG@od9Z(=m`;~qcmn^-$CjTrpg9f=a(;jHd`Fw^QgD=qG?jju2ry=&Tr z=JGs?f6M34txxUF75ss|N?;I>UO&_?E-knpC{{K9o zycur&Y4$`V=D4>`tB-#0>S_xc-0FT-S4$2UJ-VqZxuW8OkLgBUHe8F|wSH#og8yo@pI3(anF zih39W4M58eUi@`=snvIrlzV4d=-ke*PUV=ZezZDAox@Pq4m%g!@kyIilkRs69S3r1 zu9a22RwPz`>~q4&{4QO|pXj_?6a5ptTgvp8mS!eNn{PIvSzpdHFaN}D(%uZLUZ{eJ z{@JQxot|kr{LEf8)$IPA1V_x?Er|MN^Un;nv+YzcWq+}%RNgtqt$g`PE9zIU4kx*l zZ_tc;gL6#hUzj?_=a^x?uvz?qWm#%p{Qyfk8C8juMfam_ z{cviXdX*D~S^fwZ4T4j%NPjKk{|B{bVxkaH?L71EzG)Mz*>UQ6CqHK^>~2bz&|=fm+^y1W`e zUQPLvDQ;QERQ;8?n}9(v5>yEEuF|E--3KjyLW4MUv#$*?x*^-L%h)MZX1&;gq%?~8 z)1<%2An9M-%uL{~&Sv$mbfI{?QrndeiIMQf44pRMq)zJF89X^kinaPA@pEX|BvK-AIo%c}lp^yz(2RZ8zu9 ztk>t83g@lH&5n|RhHUb#U*ZEdbX4s$5AcVLUe;R=Wy)kxBcfl@m4L0dJy3 zUpsii;=!DCyyF56R_FpN-5T4SKT8(3dg+~=y>!VK=l&_Esp)pXs#h~^v3uEd2pOai zDKA#vf76o4UoiA`D0khzZ27;2-+?^ER#18WgKpZHp?9J>y>Z|s(s&ENCl8t$zcasl zX3X!@qCJy9nOC(wAJFal@AbzF)sqWeXRIa0V#K)L2b7pla)*^;#}x-823GXy=;x(LpTB=w#Hi9=))ZkK#**R+|NMh4sN| z)1nsA>l*9K>i@5kYY&RDisJ2FUV@Jm*kzYpUTXM27TD!sz7U5*Cl#$s)66K*VI)Mv zL~&BgX&kZ8vN>7Js0faZ8iEX1YG%}^rIMBpv>c5o4NIr88K&{k?>;_R)?JwT>l?oB z-gD1AkKcLR%TnRv%*YS4{ON%aM@BvUwCxL~J*ZAI8#ITu^<}0=FcV!VT{EcV&U-T& zf))uj9riR_A)_Dtm=h^&Uw`akCRV_Pp?CO#aing&>*m1Js8814R+_?oLrD>9| z41f~l);VCPy&)=0i2&b{Lw^yfu@?@((zPv`+jdM9E|2C3ntnW`?dB|6-ZBgI!|e^! zmHd8%Q`3jr-rfH6EFh0>t===#O3Jaq4!LWei znMax%D@IRN_yp5Oh`Gg0Na0Hw#hAfoCXK+uI)1-IVEg+qgWEFt*%dDk!yH;@hIz<{ zK!?mMybF*lraw5*EDEx)Nhv2mG>`+}*Ygd(f9f*_L{->3ufE)$T`~RU*>M*-zDOJJ zmKwlh6#L3nlxIn>aA+#`A~B1!C6f=frRKZYx3o3=fwg3}A})<-l*ieDBS)3h=r-&U zQW5tdgb7>psI^vD*+lDX4CLuR?)s1s?|ifWWent(oq*IO)M|zJW>T0950zUwQ3i1( z?cZ3d@3rByY@j9buv|_nZ4jVv2ou%VScL8aI%Q*VJvL*BA(-P&x(JNu#x~?N5zm`k zL}xe!=`G|F$tHunXCgHWaD~~})san(<{gz9{XXOI8t$6{L>L zw!*`*P-w{AHR-e2>kkPgMcs-@`ARg__EssU{kKZl6bVIoSMigy7ge`XF-HtpFYDG( zuYN2>mdkMuD`ht@!|)C;L+xN<$*wv7|ARB(5S`=MVYvRtC~2g*qDC;1@8f zhcahME++7d%+|gtsQ$>FGE7iMHH+~j1YdsjJAE@OWCgzh!iygCr{vW}{oiY8P&{+s zhcRd2rla*ujOP|q9(N|ZDkYou8Tfd_7}qwP*u9gQ;#miVoNJ=?cs375UUTdqRMIFH z4ywZA8gzCR)5e+^IGSI+wUMJhGM6+wKwNd=?( z?F4O2LWZSoaf+)Z_C7*DgOV7~2mdANAcKRAf!>RA^75DF70k_=bvE`^T7w|W?*y;td%I9Te*owq2qv9Xr7JYT2;lQDNg>(&M!VA~P)yM3- mUe~a@!D4pGz0 setTimeout(resolve, 3000)) + await new Promise((resolve) => setTimeout(resolve, 2000)) const formSubmissionCountCookie = cookieStore.get(BOOKMARK_SUBMISSION_COUNT_COOKIE_NAME) if (formSubmissionCountCookie?.value >= MAX_BOOKMARK_SUBMISSIONS_PER_DAY) { @@ -27,7 +27,7 @@ export async function submitBookmark(formData) { }, body: JSON.stringify({ fields: { - URL: new URL(formData.url).origin, + URL: formData.url, Email: formData.email, Date: new Date().toISOString(), Type: formData.type || 'Other' diff --git a/src/app/api/revalidate/route.js b/src/app/api/revalidate/route.js index 93f79b1b..62adf19c 100644 --- a/src/app/api/revalidate/route.js +++ b/src/app/api/revalidate/route.js @@ -3,7 +3,7 @@ import { revalidatePath } from 'next/cache' import { CONTENT_TYPES } from '@/lib/constants' export const runtime = 'edge' -export const dynamic = 'auto' +export const dynamic = 'auto' // https://www.reddit.com/r/nextjs/comments/14iu6td/revalidatepath_not_updating_generatestaticparams/ const secret = `${process.env.NEXT_REVALIDATE_SECRET}` diff --git a/src/app/bookmarks/[slug]/opengraph-image.js b/src/app/bookmarks/[slug]/opengraph-image.js index e1ae55bb..c1d1d20d 100644 --- a/src/app/bookmarks/[slug]/opengraph-image.js +++ b/src/app/bookmarks/[slug]/opengraph-image.js @@ -1,7 +1,7 @@ import { ImageResponse } from 'next/og' import { OpenGraphImage } from '@/components/og-image' -import { getMediumFont, getBoldFont } from '@/lib/utils' +import { getMediumFont, getBoldFont } from '@/lib/fonts' import { getBookmarks } from '@/lib/raindrop' import { sharedImage } from '@/app/shared-metadata' @@ -15,7 +15,7 @@ export const contentType = sharedImage.type export default async function Image({ params }) { const { slug } = params - const bookmarks = await getBookmarks() + const [bookmarks, mediumFontData, boldFontData] = await Promise.all([getBookmarks(), getMediumFont(), getBoldFont()]) const currentBookmark = bookmarks.find((bookmark) => bookmark.slug === slug) if (!currentBookmark) return null @@ -47,13 +47,13 @@ export default async function Image({ params }) { fonts: [ { name: 'SF Pro', - data: await getMediumFont(), + data: mediumFontData, style: 'normal', weight: 500 }, { name: 'SF Pro', - data: await getBoldFont(), + data: boldFontData, style: 'normal', weight: 600 } diff --git a/src/app/bookmarks/[slug]/page.js b/src/app/bookmarks/[slug]/page.js index 374466da..933e5c4a 100644 --- a/src/app/bookmarks/[slug]/page.js +++ b/src/app/bookmarks/[slug]/page.js @@ -6,7 +6,7 @@ import { PageTitle } from '@/components/page-title' import { FloatingHeader } from '@/components/floating-header' import { LoadingSpinner } from '@/components/loading-spinner' import { BookmarkList } from '@/components/bookmark-list.jsx' -import { getBookmark, getBookmarkItems, getBookmarks } from '@/lib/raindrop' +import { getBookmarkItems, getBookmarks } from '@/lib/raindrop' import { sortByProperty } from '@/lib/utils' export async function generateStaticParams() { @@ -16,17 +16,15 @@ export async function generateStaticParams() { async function fetchData(slug) { const bookmarks = await getBookmarks() - const sortedBookmarks = sortByProperty(bookmarks, 'title') const currentBookmark = bookmarks.find((bookmark) => bookmark.slug === slug) if (!currentBookmark) notFound() - const bookmark = await getBookmark(currentBookmark._id) - if (!bookmark) notFound() + const sortedBookmarks = sortByProperty(bookmarks, 'title') const bookmarkItems = await getBookmarkItems(currentBookmark._id) return { bookmarks: sortedBookmarks, - currentBookmark: bookmark.item, + currentBookmark, bookmarkItems } } diff --git a/src/app/bookmarks/opengraph-image.js b/src/app/bookmarks/opengraph-image.js index 4cd7a80c..3a55a4d9 100644 --- a/src/app/bookmarks/opengraph-image.js +++ b/src/app/bookmarks/opengraph-image.js @@ -2,7 +2,7 @@ import { ImageResponse } from 'next/og' import { OpenGraphImage } from '@/components/og-image' import { getPageSeo } from '@/lib/contentful' -import { getMediumFont, getBoldFont } from '@/lib/utils' +import { getMediumFont, getBoldFont } from '@/lib/fonts' import { sharedImage } from '@/app/shared-metadata' export const runtime = 'edge' @@ -14,9 +14,12 @@ export const size = { export const contentType = sharedImage.type export default async function Image() { - const { - seo: { title, description, ogImageTitle, ogImageSubtitle } - } = (await getPageSeo('bookmarks')) ?? {} + const [seoData = {}, mediumFontData, boldFontData] = await Promise.all([ + getPageSeo('bookmarks'), + getMediumFont(), + getBoldFont() + ]) + const { seo: { title, description, ogImageTitle, ogImageSubtitle } = {} } = seoData return new ImageResponse( ( @@ -46,13 +49,13 @@ export default async function Image() { fonts: [ { name: 'SF Pro', - data: await getMediumFont(), + data: mediumFontData, style: 'normal', weight: 500 }, { name: 'SF Pro', - data: await getBoldFont(), + data: boldFontData, style: 'normal', weight: 600 } diff --git a/src/app/journey/opengraph-image.js b/src/app/journey/opengraph-image.js index d03dd979..48ce0a12 100644 --- a/src/app/journey/opengraph-image.js +++ b/src/app/journey/opengraph-image.js @@ -2,7 +2,7 @@ import { ImageResponse } from 'next/og' import { OpenGraphImage } from '@/components/og-image' import { getPageSeo } from '@/lib/contentful' -import { getMediumFont, getBoldFont } from '@/lib/utils' +import { getMediumFont, getBoldFont } from '@/lib/fonts' import { sharedImage } from '@/app/shared-metadata' export const runtime = 'edge' @@ -14,10 +14,12 @@ export const size = { export const contentType = sharedImage.type export default async function Image() { - const seoData = (await getPageSeo('journey')) ?? {} - const { - seo: { title, description, ogImageTitle, ogImageSubtitle } - } = seoData + const [seoData = {}, mediumFontData, boldFontData] = await Promise.all([ + getPageSeo('journey'), + getMediumFont(), + getBoldFont() + ]) + const { seo: { title, description, ogImageTitle, ogImageSubtitle } = {} } = seoData return new ImageResponse( ( @@ -47,13 +49,13 @@ export default async function Image() { fonts: [ { name: 'SF Pro', - data: await getMediumFont(), + data: mediumFontData, style: 'normal', weight: 500 }, { name: 'SF Pro', - data: await getBoldFont(), + data: boldFontData, style: 'normal', weight: 600 } diff --git a/src/app/opengraph-image.js b/src/app/opengraph-image.js index 9e726982..8e8516df 100644 --- a/src/app/opengraph-image.js +++ b/src/app/opengraph-image.js @@ -1,7 +1,7 @@ import { ImageResponse } from 'next/og' import { OpenGraphImage } from '@/components/og-image' -import { getMediumFont, getBoldFont } from '@/lib/utils' +import { getMediumFont, getBoldFont } from '@/lib/fonts' import { sharedTitle, sharedDescription, sharedImage } from '@/app/shared-metadata' export const runtime = 'edge' @@ -19,6 +19,8 @@ export const contentType = sharedImage.type } */ export default async function Image() { + const [mediumFontData, boldFontData] = await Promise.all([getMediumFont(), getBoldFont()]) + return new ImageResponse( ( diff --git a/src/app/workspace/opengraph-image.js b/src/app/workspace/opengraph-image.js index 26b68d48..714fc880 100644 --- a/src/app/workspace/opengraph-image.js +++ b/src/app/workspace/opengraph-image.js @@ -2,7 +2,7 @@ import { ImageResponse } from 'next/og' import { OpenGraphImage } from '@/components/og-image' import { getPageSeo } from '@/lib/contentful' -import { getMediumFont, getBoldFont } from '@/lib/utils' +import { getMediumFont, getBoldFont } from '@/lib/fonts' import { sharedImage } from '@/app/shared-metadata' export const runtime = 'edge' @@ -14,10 +14,12 @@ export const size = { export const contentType = sharedImage.type export default async function Image() { - const seoData = (await getPageSeo('workspace')) ?? {} - const { - seo: { title, description, ogImageTitle, ogImageSubtitle } - } = seoData + const [seoData = {}, mediumFontData, boldFontData] = await Promise.all([ + getPageSeo('workspace'), + getMediumFont(), + getBoldFont() + ]) + const { seo: { title, description, ogImageTitle, ogImageSubtitle } = {} } = seoData return new ImageResponse( ( @@ -50,13 +52,13 @@ export default async function Image() { fonts: [ { name: 'SF Pro', - data: await getMediumFont(), + data: mediumFontData, style: 'normal', weight: 500 }, { name: 'SF Pro', - data: await getBoldFont(), + data: boldFontData, style: 'normal', weight: 600 } diff --git a/src/app/workspace/page.js b/src/app/workspace/page.js index f9f2266b..e4b2d6ee 100644 --- a/src/app/workspace/page.js +++ b/src/app/workspace/page.js @@ -38,8 +38,7 @@ export default async function Workspace() { /> - -

+
diff --git a/src/app/writing/[slug]/opengraph-image.js b/src/app/writing/[slug]/opengraph-image.js index fa176b60..5646a5da 100644 --- a/src/app/writing/[slug]/opengraph-image.js +++ b/src/app/writing/[slug]/opengraph-image.js @@ -2,7 +2,7 @@ import { ImageResponse } from 'next/og' import { OpenGraphImage } from '@/components/og-image' import { getWritingSeo } from '@/lib/contentful' -import { getMediumFont, getBoldFont } from '@/lib/utils' +import { getMediumFont, getBoldFont } from '@/lib/fonts' import { sharedImage } from '@/app/shared-metadata' export const runtime = 'edge' @@ -15,9 +15,12 @@ export const contentType = sharedImage.type export default async function Image({ params }) { const { slug } = params - const seoData = await getWritingSeo(slug) + const [seoData, mediumFontData, boldFontData] = await Promise.all([ + getWritingSeo(slug), + getMediumFont(), + getBoldFont() + ]) if (!seoData) return null - const { seo: { title, ogImageTitle, ogImageSubtitle } } = seoData @@ -35,13 +38,13 @@ export default async function Image({ params }) { fonts: [ { name: 'SF Pro', - data: await getMediumFont(), + data: mediumFontData, style: 'normal', weight: 500 }, { name: 'SF Pro', - data: await getBoldFont(), + data: boldFontData, style: 'normal', weight: 600 } diff --git a/src/app/writing/layout.js b/src/app/writing/layout.js index d6946173..1870392c 100644 --- a/src/app/writing/layout.js +++ b/src/app/writing/layout.js @@ -8,12 +8,12 @@ import { getSortedPosts } from '@/lib/utils' async function fetchData() { const allPosts = await getAllPosts() - return { allPosts } + const sortedPosts = getSortedPosts(allPosts) + return { sortedPosts } } export default async function WritingLayout({ children }) { - const { allPosts } = await fetchData() - const sortedPosts = getSortedPosts(allPosts) + const { sortedPosts } = await fetchData() return ( <> diff --git a/src/app/writing/opengraph-image.js b/src/app/writing/opengraph-image.js index f17465c6..d02c45e5 100644 --- a/src/app/writing/opengraph-image.js +++ b/src/app/writing/opengraph-image.js @@ -2,7 +2,7 @@ import { ImageResponse } from 'next/og' import { OpenGraphImage } from '@/components/og-image' import { getPageSeo } from '@/lib/contentful' -import { getMediumFont, getBoldFont } from '@/lib/utils' +import { getMediumFont, getBoldFont } from '@/lib/fonts' import { sharedImage } from '@/app/shared-metadata' export const runtime = 'edge' @@ -14,9 +14,12 @@ export const size = { export const contentType = sharedImage.type export default async function Image() { - const { - seo: { title, description, ogImageTitle, ogImageSubtitle } - } = (await getPageSeo('writing')) ?? {} + const [seoData = {}, mediumFontData, boldFontData] = await Promise.all([ + getPageSeo('writing'), + getMediumFont(), + getBoldFont() + ]) + const { seo: { title, description, ogImageTitle, ogImageSubtitle } = {} } = seoData return new ImageResponse( ( @@ -47,13 +50,13 @@ export default async function Image() { fonts: [ { name: 'SF Pro', - data: await getMediumFont(), + data: mediumFontData, style: 'normal', weight: 500 }, { name: 'SF Pro', - data: await getBoldFont(), + data: boldFontData, style: 'normal', weight: 600 } diff --git a/src/app/writing/page.js b/src/app/writing/page.js index 9ac54633..7a99a2f7 100644 --- a/src/app/writing/page.js +++ b/src/app/writing/page.js @@ -9,12 +9,12 @@ import { getSortedPosts } from '@/lib/utils' async function fetchData() { const allPosts = await getAllPosts() - return { allPosts } + const sortedPosts = getSortedPosts(allPosts) + return { sortedPosts } } export default async function Writing() { - const { allPosts } = await fetchData() - const sortedPosts = getSortedPosts(allPosts) + const { sortedPosts } = await fetchData() return ( diff --git a/src/components/journey-card.js b/src/components/journey-card.js index 9a8f3d67..3f58dc32 100644 --- a/src/components/journey-card.js +++ b/src/components/journey-card.js @@ -6,7 +6,7 @@ export const JourneyCard = ({ title, description, image, index }) => { {title} {description && {description}} {image?.url && ( -
+
{image.title - {hostname} has been submitted. Thank you! + {values.url} has been submitted. Thank you! ) }) diff --git a/src/components/writing-link.js b/src/components/writing-link.js index 3f79dfe0..bf6d643d 100644 --- a/src/components/writing-link.js +++ b/src/components/writing-link.js @@ -1,14 +1,9 @@ -'use client' - import Link from 'next/link' -import { usePathname } from 'next/navigation' import { LazyMotion, domAnimation, m } from 'framer-motion' import { cn, getDateTimeFormat, viewCountFormatter } from '@/lib/utils' -export const WritingLink = ({ post, viewCount, isMobile }) => { - const pathname = usePathname() - const isActive = pathname === `/writing/${post.slug}` +export const WritingLink = ({ post, viewCount, isMobile, isActive }) => { const date = post.date || post.sys.firstPublishedAt const formattedDate = getDateTimeFormat(date) const formattedViewCount = viewCount ? viewCountFormatter.format(viewCount) : null diff --git a/src/components/writing/writing-list-layout.js b/src/components/writing/writing-list-layout.js index 7769a040..04a9db55 100644 --- a/src/components/writing/writing-list-layout.js +++ b/src/components/writing/writing-list-layout.js @@ -1,17 +1,22 @@ 'use client' +import { usePathname } from 'next/navigation' + import { WritingLink } from '@/components/writing-link' import { useViewData } from '@/hooks/useViewData' import { cn } from '@/lib/utils' export const WritingListLayout = ({ list, isMobile }) => { const viewData = useViewData() + const pathname = usePathname() return (
{list.map((post) => { const viewCount = viewData?.find((item) => item.slug === post.slug)?.view_count - return + const isActive = pathname === `/writing/${post.slug}` + + return })}
) diff --git a/src/lib/contentful.js b/src/lib/contentful.js index 8c8a4c0c..8896cda3 100644 --- a/src/lib/contentful.js +++ b/src/lib/contentful.js @@ -1,262 +1,291 @@ +import { cache } from 'react' +import 'server-only' + import { isDevelopment } from '@/lib/utils' -async function fetchGraphQL(query, preview = isDevelopment) { - const res = await fetch(`https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${ - preview ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN : process.env.CONTENTFUL_ACCESS_TOKEN - }` - }, - body: JSON.stringify({ query }) - }) - if (!res.ok) return undefined - return res.json() -} +const fetchGraphQL = cache(async (query, preview = isDevelopment) => { + try { + const res = await fetch(`https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${ + preview ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN : process.env.CONTENTFUL_ACCESS_TOKEN + }` + }, + body: JSON.stringify({ query }) + }) -export async function getAllPosts(preview = isDevelopment) { - const entries = await fetchGraphQL( - `query { - postCollection(preview: ${preview}) { - items { - title - slug - date - sys { - firstPublishedAt - publishedAt - } - } - } - }`, - preview - ) + if (!res.ok) return null + return res.json() + } catch (error) { + console.info(error) + return null + } +}) - return entries?.data?.postCollection?.items ?? [] -} - -/* export async function getLast3Posts(preview = isDevelopment) { - const entries = await fetchGraphQL( - `query { - postCollection(order: date_DESC, preview: ${preview}, limit: 3) { - items { - title - slug - date - sys { - firstPublishedAt +export const getAllPosts = cache(async (preview = isDevelopment) => { + try { + const entries = await fetchGraphQL( + `query { + postCollection(preview: ${preview}) { + items { + title + slug + date + sys { + firstPublishedAt + publishedAt + } } } - } - }`, - preview - ) + }`, + preview + ) - return entries?.data?.postCollection?.items ?? [] -} */ + return entries?.data?.postCollection?.items ?? [] + } catch (error) { + console.info(error) + return [] + } +}) -export async function getPost(slug, preview = isDevelopment) { - const entry = await fetchGraphQL( - `query { - postCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { - items { - title - slug - date - seo { +export const getPost = cache(async (slug, preview = isDevelopment) => { + try { + const entry = await fetchGraphQL( + `query { + postCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { + items { title - description - } - content { - json - links { - assets { - block { - sys { - id - } - url - title - width - height - description - } - } - entries { - inline { - sys { - id - } - __typename - ... on ContentEmbed { - title - embedUrl - type - } - ... on CodeBlock { + slug + date + seo { + title + description + } + content { + json + links { + assets { + block { + sys { + id + } + url title - code + width + height + description } - ... on Tweet { - id + } + entries { + inline { + sys { + id + } + __typename + ... on ContentEmbed { + title + embedUrl + type + } + ... on CodeBlock { + title + code + } + ... on Tweet { + id + } } } } } - } - sys { - firstPublishedAt - publishedAt + sys { + firstPublishedAt + publishedAt + } } } - } - }`, - preview - ) + }`, + preview + ) - return entry?.data?.postCollection?.items?.[0] -} + return entry?.data?.postCollection?.items?.[0] ?? null + } catch (error) { + console.info(error) + return null + } +}) -export async function getWritingSeo(slug, preview = isDevelopment) { - const entry = await fetchGraphQL( - `query { - postCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { - items { - date - seo { - title - description - ogImageTitle - ogImageSubtitle - } - sys { - firstPublishedAt - publishedAt +export const getWritingSeo = cache(async (slug, preview = isDevelopment) => { + try { + const entry = await fetchGraphQL( + `query { + postCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { + items { + date + seo { + title + description + ogImageTitle + ogImageSubtitle + } + sys { + firstPublishedAt + publishedAt + } } } - } - }`, - preview - ) + }`, + preview + ) - return entry?.data?.postCollection?.items?.[0] -} + return entry?.data?.postCollection?.items?.[0] ?? null + } catch (error) { + console.info(error) + return null + } +}) -export async function getPageSeo(slug, preview = isDevelopment) { - const entry = await fetchGraphQL( - `query { - pageCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { - items { - seo { - title - description - ogImageTitle - ogImageSubtitle +export const getPageSeo = cache(async (slug, preview = isDevelopment) => { + try { + const entry = await fetchGraphQL( + `query { + pageCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { + items { + seo { + title + description + ogImageTitle + ogImageSubtitle + } } } - } - }`, - preview - ) + }`, + preview + ) - return entry?.data?.pageCollection?.items?.[0] -} + return entry?.data?.pageCollection?.items?.[0] ?? null + } catch (error) { + console.info(error) + return null + } +}) -export async function getAllPageSlugs(preview = isDevelopment) { - const entries = await fetchGraphQL( - `query { - pageCollection(preview: ${preview}) { - items { - slug - hasCustomPage - sys { - id - firstPublishedAt - publishedAt +export const getAllPageSlugs = cache(async (preview = isDevelopment) => { + try { + const entries = await fetchGraphQL( + `query { + pageCollection(preview: ${preview}) { + items { + slug + hasCustomPage + sys { + id + firstPublishedAt + publishedAt + } } } - } - }`, - preview - ) + }`, + preview + ) - return entries?.data?.pageCollection?.items ?? [] -} + return entries?.data?.pageCollection?.items ?? [] + } catch (error) { + console.info(error) + return [] + } +}) -export async function getAllPostSlugs(preview = isDevelopment) { - const entries = await fetchGraphQL( - `query { - postCollection(preview: ${preview}) { - items { - slug +export const getAllPostSlugs = cache(async (preview = isDevelopment) => { + try { + const entries = await fetchGraphQL( + `query { + postCollection(preview: ${preview}) { + items { + slug + } } - } - }`, - preview - ) + }`, + preview + ) - return entries?.data?.postCollection?.items ?? [] -} + return entries?.data?.postCollection?.items ?? [] + } catch (error) { + console.info(error) + return [] + } +}) -export async function getPage(slug, preview = isDevelopment) { - const entry = await fetchGraphQL( - `query { - pageCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { - items { - title - slug - content { - json - links { - assets { - block { - sys { - id +export const getPage = cache(async (slug, preview = isDevelopment) => { + try { + const entry = await fetchGraphQL( + `query { + pageCollection(where: { slug: "${slug}" }, preview: ${preview}, limit: 1) { + items { + title + slug + content { + json + links { + assets { + block { + sys { + id + } + url + title + width + height + description } - url - title - width - height - description } } } - } - sys { - id - firstPublishedAt - publishedAt + sys { + id + firstPublishedAt + publishedAt + } } } - } - }`, - preview - ) + }`, + preview + ) - return entry?.data?.pageCollection?.items?.[0] -} + return entry?.data?.pageCollection?.items?.[0] ?? null + } catch (error) { + console.info(error) + return null + } +}) -export async function getAllLogbook(preview = isDevelopment) { - const entries = await fetchGraphQL( - `query { - logbookCollection(order: date_DESC, preview: ${preview}) { - items { - title - date - description - image { - url +export const getAllLogbook = cache(async (preview = isDevelopment) => { + try { + const entries = await fetchGraphQL( + `query { + logbookCollection(order: date_DESC, preview: ${preview}) { + items { title + date description - width - height + image { + url + title + description + width + height + } } } - } - }`, - preview - ) + }`, + preview + ) - return entries?.data?.logbookCollection?.items ?? [] -} + return entries?.data?.logbookCollection?.items ?? [] + } catch (error) { + console.info(error) + return [] + } +}) diff --git a/src/lib/fonts.js b/src/lib/fonts.js new file mode 100644 index 00000000..67d2fc13 --- /dev/null +++ b/src/lib/fonts.js @@ -0,0 +1,24 @@ +import { cache } from 'react' +import 'server-only' + +/** + * Retrieves the SpaceGrotesk-Medium font file asynchronously. + * It returns a Promise that resolves to the font file's array buffer. + * @returns A Promise resolving to the SpaceGrotesk-Medium font file as an array buffer. + */ +export const getMediumFont = cache(async () => { + const response = await fetch(new URL('@/assets/fonts/SpaceGrotesk-Medium.ttf', import.meta.url)) + const font = await response.arrayBuffer() + return font +}) + +/** + * Retrieves the SpaceGrotesk-SemiBold font file asynchronously. + * It returns a Promise that resolves to the font file's array buffer. + * @returns A Promise resolving to the SpaceGrotesk-SemiBold font file as an array buffer. + */ +export const getBoldFont = cache(async () => { + const response = await fetch(new URL('@/assets/fonts/SpaceGrotesk-SemiBold.ttf', import.meta.url)) + const font = await response.arrayBuffer() + return font +}) diff --git a/src/lib/github.js b/src/lib/github.js deleted file mode 100644 index 56fd7e7d..00000000 --- a/src/lib/github.js +++ /dev/null @@ -1,9 +0,0 @@ -export async function getStarredRepos() { - try { - const response = await fetch(`https://api.github.com/users/suyalcinkaya/starred?per_page=100&page=1`) - return await response.json() - } catch (error) { - console.info(error) - return null - } -} diff --git a/src/lib/raindrop.js b/src/lib/raindrop.js index 12837e9d..9251cf6a 100644 --- a/src/lib/raindrop.js +++ b/src/lib/raindrop.js @@ -1,3 +1,6 @@ +import { cache } from 'react' +import 'server-only' + import { COLLECTION_IDS } from '@/lib/constants' const options = { @@ -10,7 +13,7 @@ const options = { const RAINDROP_API_URL = 'https://api.raindrop.io/rest/v1' -export async function getBookmarkItems(id, pageIndex = 0) { +export const getBookmarkItems = cache(async (id, pageIndex = 0) => { try { const response = await fetch( `${RAINDROP_API_URL}/raindrops/${id}?` + @@ -25,9 +28,9 @@ export async function getBookmarkItems(id, pageIndex = 0) { console.info(error) return null } -} +}) -export async function getBookmarks() { +export const getBookmarks = cache(async () => { try { const response = await fetch(`${RAINDROP_API_URL}/collections`, options) const bookmarks = await response.json() @@ -37,9 +40,9 @@ export async function getBookmarks() { console.info(error) return null } -} +}) -export async function getBookmark(id) { +export const getBookmark = cache(async (id) => { try { const response = await fetch(`${RAINDROP_API_URL}/collection/${id}`, options) return await response.json() @@ -47,4 +50,4 @@ export async function getBookmark(id) { console.info(error) return null } -} +}) diff --git a/src/lib/utils.js b/src/lib/utils.js index 2a7fc7a4..c977fad3 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,3 +1,4 @@ +import { cache } from 'react' import { twMerge } from 'tailwind-merge' import { cx } from 'classix' @@ -49,28 +50,6 @@ export const getDateTimeFormat = (date) => { */ export const dasherize = (text) => String(text).replace(/ +/g, '-').toLowerCase() -/** - * Retrieves the SpaceGrotesk-Medium font file asynchronously. - * It returns a Promise that resolves to the font file's array buffer. - * @returns A Promise resolving to the SpaceGrotesk-Medium font file as an array buffer. - */ -export const getMediumFont = async () => { - const response = await fetch(new URL('@/assets/fonts/SpaceGrotesk-Medium.ttf', import.meta.url)) - const font = await response.arrayBuffer() - return font -} - -/** - * Retrieves the SpaceGrotesk-SemiBold font file asynchronously. - * It returns a Promise that resolves to the font file's array buffer. - * @returns A Promise resolving to the SpaceGrotesk-SemiBold font file as an array buffer. - */ -export const getBoldFont = async () => { - const response = await fetch(new URL('@/assets/fonts/SpaceGrotesk-SemiBold.ttf', import.meta.url)) - const font = await response.arrayBuffer() - return font -} - /** * Checks if the current environment is set to development mode. * The function compares the value of the `NODE_ENV` environment variable with 'development'. @@ -85,7 +64,7 @@ export const isDevelopment = process.env.NODE_ENV === 'development' * @param prop The property name used for sorting the objects. * @returns The sorted array in ascending order based on the specified property. */ -export const sortByProperty = (arr, prop) => { +export const sortByProperty = cache((arr, prop) => { return arr?.sort((a, b) => { const itemA = a[prop].toUpperCase() const itemB = b[prop].toUpperCase() @@ -98,7 +77,7 @@ export const sortByProperty = (arr, prop) => { return 0 }) -} +}) /** * Sorts an array of blog post objects based on their date field (only for old blog posts) or publication dates in descending order. @@ -107,13 +86,13 @@ export const sortByProperty = (arr, prop) => { * @param posts The array of blog post objects to be sorted. * @returns The sorted array of blog posts in descending order based on their publication dates. */ -export const getSortedPosts = (posts) => { +export const getSortedPosts = cache((posts) => { return posts.sort((a, b) => { const dateA = a.date || a.sys.firstPublishedAt const dateB = b.date || b.sys.firstPublishedAt return new Date(dateB) - new Date(dateA) }) -} +}) /** * Creates an instance of the DateTimeFormat object with 'en-US' locale,