[{"data":1,"prerenderedAt":3074},["ShallowReactive",2],{"post-\u002Ffr\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages":3,"post-link-fr-external":3069},{"id":4,"title":5,"body":6,"contentId":3062,"date":3063,"description":3064,"extension":3065,"meta":3066,"navigation":369,"path":3067,"rawbody":3068,"robots":3069,"schemaOrg":3069,"seo":3070,"sitemap":3071,"stem":3072,"__hash__":3073},"posts\u002Ffr\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages.md","Créer un raccourcisseur d'URL avec Nitro et Cloudflare Pages",{"type":7,"value":8,"toc":3055},"minimark",[9,36,43,52,65,79,82,142,147,154,183,190,212,219,234,242,246,249,267,272,291,804,813,824,831,1065,1074,1078,1085,1099,1117,1683,1697,1710,1721,1735,3051],[10,11,12,13,17,18,27,28,35],"p",{},"Dans cet article, nous allons développer un ",[14,15,16],"strong",{},"raccourcisseur d'URL"," en utilisant ",[19,20,24],"a",{"href":21,"rel":22},"https:\u002F\u002Fnitro.unjs.io",[23],"nofollow",[14,25,26],{},"Nitro"," et le déployer sur ",[19,29,32],{"href":30,"rel":31},"https:\u002F\u002Fpages.cloudflare.com\u002F",[23],[14,33,34],{},"Cloudflare Pages",".",[10,37,38,39],{},"Le code source est accessible sur ",[40,41,35],"git-hub-link",{"repo":42},"barbapapazes\u002Furl-shortener",[10,44,45,47,48,51],{},[14,46,26],{}," représente une nouvelle génération de ",[14,49,50],{},"kit d'outils serveur",". Il nous permet de construire des serveurs web avec toutes les fonctionnalités nécessaires et de les déployer à notre convenance.",[10,53,54,56,57,64],{},[14,55,34],{}," est une plateforme conçue pour construire et héberger des sites web sur le edge. Elle prend en charge des services comme ",[19,58,61],{"href":59,"rel":60},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F",[23],[14,62,63],{},"KV"," pour créer des applications complètes et à état.",[10,66,67,68,70,71,74,75,78],{},"Notre projet est un ",[14,69,16],{}," simple qui facilite la conversion d'une longue URL en une version raccourcie. Nous allons utiliser le ",[14,72,73],{},"Cloudflare KV"," pour stocker les URL et le ",[14,76,77],{},"serveur Nitro"," pour gérer les requêtes.",[10,80,81],{},"Nous allons utiliser :",[83,84,85,102,116,132],"ul",{},[86,87,88,95,96,98,99,35],"li",{},[19,89,92],{"href":90,"rel":91},"https:\u002F\u002Funstorage.unjs.io",[23],[14,93,94],{},"unstorage"," pour rationaliser le processus de développement en abstraire la couche ",[14,97,63],{},", éliminant ainsi le besoin du ",[14,100,101],{},"Cloudflare Wrangler CLI",[86,103,104,111,112,115],{},[19,105,108],{"href":106,"rel":107},"https:\u002F\u002Fohash.unjs.io",[23],[14,109,110],{},"ohash"," pour générer un ",[14,113,114],{},"hash"," à partir de l'URL afin de prévenir les collisions.",[86,117,118,125,126,17,129,35],{},[19,119,122],{"href":120,"rel":121},"https:\u002F\u002Fnanojsx.io\u002F",[23],[14,123,124],{},"nanojsx"," pour construire les ",[14,127,128],{},"pages HTML",[14,130,131],{},"TSX",[86,133,134,141],{},[19,135,138],{"href":136,"rel":137},"https:\u002F\u002Fpicocss.com",[23],[14,139,140],{},"pico.css"," pour styliser l'application.",[143,144,146],"h2",{"id":145},"initialisation-du-projet","Initialisation du Projet",[10,148,149,150,153],{},"Tout d'abord, créez un nouveau ",[14,151,152],{},"projet Nitro"," :",[155,156,161],"pre",{"className":157,"code":158,"language":159,"meta":160,"style":160},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx giget@latest nitro url-shortener\n","bash","",[162,163,164],"code",{"__ignoreMap":160},[165,166,169,173,177,180],"span",{"class":167,"line":168},"line",1,[165,170,172],{"class":171},"sBMFI","npx",[165,174,176],{"class":175},"sfazB"," giget@latest",[165,178,179],{"class":175}," nitro",[165,181,182],{"class":175}," url-shortener\n",[10,184,185,186,189],{},"Ensuite, naviguez jusqu'au projet et installez les ",[14,187,188],{},"dépendances"," requises :",[155,191,193],{"className":157,"code":192,"language":159,"meta":160,"style":160},"cd url-shortener\nnpm install\n",[162,194,195,203],{"__ignoreMap":160},[165,196,197,201],{"class":167,"line":168},[165,198,200],{"class":199},"s2Zo4","cd",[165,202,182],{"class":175},[165,204,206,209],{"class":167,"line":205},2,[165,207,208],{"class":171},"npm",[165,210,211],{"class":175}," install\n",[10,213,214,215,218],{},"Démarrez le ",[14,216,217],{},"serveur de développement"," pour voir la page par défaut de Nitro :",[155,220,222],{"className":157,"code":221,"language":159,"meta":160,"style":160},"npm run dev\n",[162,223,224],{"__ignoreMap":160},[165,225,226,228,231],{"class":167,"line":168},[165,227,208],{"class":171},[165,229,230],{"class":175}," run",[165,232,233],{"class":175}," dev\n",[10,235,236,237,241],{},"Ouvrez votre navigateur et visitez ",[19,238,239],{"href":239,"rel":240},"http:\u002F\u002Flocalhost:3000",[23]," pour vérifier la fonctionnalité.",[143,243,245],{"id":244},"construction-du-raccourcisseur-durl","Construction du Raccourcisseur d'URL",[10,247,248],{},"Dans un premier temps, installez les paquets nécessaires :",[155,250,252],{"className":157,"code":251,"language":159,"meta":160,"style":160},"npm install ohash nano-jsx\n",[162,253,254],{"__ignoreMap":160},[165,255,256,258,261,264],{"class":167,"line":168},[165,257,208],{"class":171},[165,259,260],{"class":175}," install",[165,262,263],{"class":175}," ohash",[165,265,266],{"class":175}," nano-jsx\n",[268,269,271],"h3",{"id":270},"générer-une-url-raccourcie","Générer une URL Raccourcie",[10,273,274,275,278,279,282,283,286,287,290],{},"Créez une ",[14,276,277],{},"route"," nommée ",[162,280,281],{},"index.get.tsx"," dans le répertoire ",[162,284,285],{},"server\u002Froutes",". Cela servira de ",[14,288,289],{},"page d'accueil"," de notre raccourcisseur d'URL où les utilisateurs peuvent générer une URL raccourcie à partir d'une longue.",[155,292,297],{"className":293,"code":294,"filename":295,"language":296,"meta":160,"style":160},"language-tsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { h, Helmet, renderSSR } from 'nano-jsx' \u002F\u002F le `h` est crucial ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineLazyEventHandler(() => {\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Raccourcisseur d'URL avec Nitro\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Raccourcir une URL\u003C\u002Fh2>\n        \u003Cform action=\"\u002Fcreate\" method=\"POST\">\n          \u003Cinput type=\"url\" name=\"url\" placeholder=\"URL à raccourcir\" autocomplete=\"off\" \u002F>\n          \u003Cbutton type=\"submit\">Créer\u003C\u002Fbutton>\n        \u003C\u002Fform>\n      \u003C\u002Fdiv>\n    )\n  }\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body, head } = Helmet.SSR(app)\n\n  const page = withTemplate({\n    body,\n    head,\n  })\n\n  return defineEventHandler(() => {\n    return page\n  })\n})\n","server\u002Froutes\u002Findex.get.tsx","tsx",[162,298,299,343,364,371,396,415,425,437,448,470,480,498,534,592,622,631,641,647,653,679,712,717,734,743,751,759,764,781,789,796],{"__ignoreMap":160},[165,300,301,305,309,313,316,319,321,324,327,330,333,336,339],{"class":167,"line":168},[165,302,304],{"class":303},"s7zQu","import",[165,306,308],{"class":307},"sMK4o"," {",[165,310,312],{"class":311},"sTEyZ"," h",[165,314,315],{"class":307},",",[165,317,318],{"class":311}," Helmet",[165,320,315],{"class":307},[165,322,323],{"class":311}," renderSSR",[165,325,326],{"class":307}," }",[165,328,329],{"class":303}," from",[165,331,332],{"class":307}," '",[165,334,335],{"class":175},"nano-jsx",[165,337,338],{"class":307},"'",[165,340,342],{"class":341},"sHwdD"," \u002F\u002F le `h` est crucial ici\n",[165,344,345,347,349,352,354,356,358,361],{"class":167,"line":205},[165,346,304],{"class":303},[165,348,308],{"class":307},[165,350,351],{"class":311}," withTemplate",[165,353,326],{"class":307},[165,355,329],{"class":303},[165,357,332],{"class":307},[165,359,360],{"class":175},"..\u002Fresources\u002Ftemplate",[165,362,363],{"class":307},"'\n",[165,365,367],{"class":167,"line":366},3,[165,368,370],{"emptyLinePlaceholder":369},true,"\n",[165,372,374,377,380,383,386,389,393],{"class":167,"line":373},4,[165,375,376],{"class":303},"export",[165,378,379],{"class":303}," default",[165,381,382],{"class":199}," defineLazyEventHandler",[165,384,385],{"class":311},"(",[165,387,388],{"class":307},"()",[165,390,392],{"class":391},"spNyl"," =>",[165,394,395],{"class":307}," {\n",[165,397,399,402,405,408,411,413],{"class":167,"line":398},5,[165,400,401],{"class":391},"  const",[165,403,404],{"class":311}," App",[165,406,407],{"class":307}," =",[165,409,410],{"class":307}," ()",[165,412,392],{"class":391},[165,414,395],{"class":307},[165,416,418,421],{"class":167,"line":417},6,[165,419,420],{"class":303},"    return",[165,422,424],{"class":423},"swJcz"," (\n",[165,426,428,431,434],{"class":167,"line":427},7,[165,429,430],{"class":307},"      \u003C",[165,432,433],{"class":423},"div",[165,435,436],{"class":307},">\n",[165,438,440,443,446],{"class":167,"line":439},8,[165,441,442],{"class":307},"        \u003C",[165,444,445],{"class":171},"Helmet",[165,447,436],{"class":307},[165,449,451,454,457,460,463,466,468],{"class":167,"line":450},9,[165,452,453],{"class":307},"          \u003C",[165,455,456],{"class":423},"title",[165,458,459],{"class":307},">",[165,461,462],{"class":311},"Raccourcisseur d'URL avec Nitro",[165,464,465],{"class":307},"\u003C\u002F",[165,467,456],{"class":423},[165,469,436],{"class":307},[165,471,473,476,478],{"class":167,"line":472},10,[165,474,475],{"class":307},"        \u003C\u002F",[165,477,445],{"class":171},[165,479,436],{"class":307},[165,481,483,485,487,489,492,494,496],{"class":167,"line":482},11,[165,484,442],{"class":307},[165,486,143],{"class":423},[165,488,459],{"class":307},[165,490,491],{"class":311},"Raccourcir une URL",[165,493,465],{"class":307},[165,495,143],{"class":423},[165,497,436],{"class":307},[165,499,501,503,506,509,512,515,518,520,523,525,527,530,532],{"class":167,"line":500},12,[165,502,442],{"class":307},[165,504,505],{"class":423},"form",[165,507,508],{"class":391}," action",[165,510,511],{"class":307},"=",[165,513,514],{"class":307},"\"",[165,516,517],{"class":175},"\u002Fcreate",[165,519,514],{"class":307},[165,521,522],{"class":391}," method",[165,524,511],{"class":307},[165,526,514],{"class":307},[165,528,529],{"class":175},"POST",[165,531,514],{"class":307},[165,533,436],{"class":307},[165,535,537,539,542,545,547,549,552,554,557,559,561,563,565,568,570,572,575,577,580,582,584,587,589],{"class":167,"line":536},13,[165,538,453],{"class":307},[165,540,541],{"class":423},"input",[165,543,544],{"class":391}," type",[165,546,511],{"class":307},[165,548,514],{"class":307},[165,550,551],{"class":175},"url",[165,553,514],{"class":307},[165,555,556],{"class":391}," name",[165,558,511],{"class":307},[165,560,514],{"class":307},[165,562,551],{"class":175},[165,564,514],{"class":307},[165,566,567],{"class":391}," placeholder",[165,569,511],{"class":307},[165,571,514],{"class":307},[165,573,574],{"class":175},"URL à raccourcir",[165,576,514],{"class":307},[165,578,579],{"class":391}," autocomplete",[165,581,511],{"class":307},[165,583,514],{"class":307},[165,585,586],{"class":175},"off",[165,588,514],{"class":307},[165,590,591],{"class":307}," \u002F>\n",[165,593,595,597,600,602,604,606,609,611,613,616,618,620],{"class":167,"line":594},14,[165,596,453],{"class":307},[165,598,599],{"class":423},"button",[165,601,544],{"class":391},[165,603,511],{"class":307},[165,605,514],{"class":307},[165,607,608],{"class":175},"submit",[165,610,514],{"class":307},[165,612,459],{"class":307},[165,614,615],{"class":311},"Créer",[165,617,465],{"class":307},[165,619,599],{"class":423},[165,621,436],{"class":307},[165,623,625,627,629],{"class":167,"line":624},15,[165,626,475],{"class":307},[165,628,505],{"class":423},[165,630,436],{"class":307},[165,632,634,637,639],{"class":167,"line":633},16,[165,635,636],{"class":307},"      \u003C\u002F",[165,638,433],{"class":423},[165,640,436],{"class":307},[165,642,644],{"class":167,"line":643},17,[165,645,646],{"class":423},"    )\n",[165,648,650],{"class":167,"line":649},18,[165,651,652],{"class":307},"  }\n",[165,654,656,658,661,663,665,667,670,673,676],{"class":167,"line":655},19,[165,657,401],{"class":391},[165,659,660],{"class":311}," app",[165,662,407],{"class":307},[165,664,323],{"class":199},[165,666,385],{"class":423},[165,668,669],{"class":307},"\u003C",[165,671,672],{"class":171},"App",[165,674,675],{"class":307}," \u002F>",[165,677,678],{"class":423},")\n",[165,680,682,684,686,689,691,694,696,698,700,702,705,707,710],{"class":167,"line":681},20,[165,683,401],{"class":391},[165,685,308],{"class":307},[165,687,688],{"class":311}," body",[165,690,315],{"class":307},[165,692,693],{"class":311}," head",[165,695,326],{"class":307},[165,697,407],{"class":307},[165,699,318],{"class":311},[165,701,35],{"class":307},[165,703,704],{"class":199},"SSR",[165,706,385],{"class":423},[165,708,709],{"class":311},"app",[165,711,678],{"class":423},[165,713,715],{"class":167,"line":714},21,[165,716,370],{"emptyLinePlaceholder":369},[165,718,720,722,725,727,729,731],{"class":167,"line":719},22,[165,721,401],{"class":391},[165,723,724],{"class":311}," page",[165,726,407],{"class":307},[165,728,351],{"class":199},[165,730,385],{"class":423},[165,732,733],{"class":307},"{\n",[165,735,737,740],{"class":167,"line":736},23,[165,738,739],{"class":311},"    body",[165,741,742],{"class":307},",\n",[165,744,746,749],{"class":167,"line":745},24,[165,747,748],{"class":311},"    head",[165,750,742],{"class":307},[165,752,754,757],{"class":167,"line":753},25,[165,755,756],{"class":307},"  }",[165,758,678],{"class":423},[165,760,762],{"class":167,"line":761},26,[165,763,370],{"emptyLinePlaceholder":369},[165,765,767,770,773,775,777,779],{"class":167,"line":766},27,[165,768,769],{"class":303},"  return",[165,771,772],{"class":199}," defineEventHandler",[165,774,385],{"class":423},[165,776,388],{"class":307},[165,778,392],{"class":391},[165,780,395],{"class":307},[165,782,784,786],{"class":167,"line":783},28,[165,785,420],{"class":303},[165,787,788],{"class":311}," page\n",[165,790,792,794],{"class":167,"line":791},29,[165,793,756],{"class":307},[165,795,678],{"class":423},[165,797,799,802],{"class":167,"line":798},30,[165,800,801],{"class":307},"}",[165,803,678],{"class":311},[10,805,806,807,810,811,35],{},"Cette route présentera un formulaire permettant aux utilisateurs de saisir une URL à raccourcir. Lors de la soumission du formulaire, une ",[14,808,809],{},"requête POST"," est envoyée à la route ",[162,812,517],{},[10,814,815,816,819,820,823],{},"Un ",[14,817,818],{},"gestionnaire d'événements paresseux"," est utilisé pour générer la vue une seule fois, lorsque la requête atteint le serveur. La réponse est ensuite ",[14,821,822],{},"mise en cache en mémoire"," et réutilisée pour les requêtes futures, réduisant ainsi la charge computationnelle.",[10,825,826,827,830],{},"La fonction ",[162,828,829],{},"withTemplate"," sert d'utilitaire que nous devons construire.",[155,832,837],{"className":833,"code":834,"filename":835,"language":836,"meta":160,"style":160},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","interface LayoutProps {\n  body: string\n  head: string[]\n}\n\nexport function withTemplate(props: LayoutProps) {\n  const { head, body } = props\n\n  return \u002F* html *\u002F`\u003Chtml>\n      \u003Chead>\n       \u003Clink\n          rel=\"stylesheet\"\n          href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@picocss\u002Fpico@2\u002Fcss\u002Fpico.min.css\"\n        \u002F>\n        ${head.join('\\n')}\n      \u003C\u002Fhead>\n      \u003Cbody>\n        \u003Cheader class=\"container\">\n          \u003Ch1>\n            \u003Ca href=\"\u002F\">Raccourcisseur d'URL avec Nitro\u003C\u002Fa>\n          \u003C\u002Fh1>\n        \u003C\u002Fheader>\n        \u003Cmain class=\"container\">\n          ${body}\n        \u003C\u002Fmain>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>`\n}\n","server\u002Fresources\u002Ftemplate.tsx","ts",[162,838,839,849,860,873,878,882,906,925,929,942,947,952,957,962,967,993,998,1003,1008,1013,1018,1023,1028,1033,1043,1048,1053,1061],{"__ignoreMap":160},[165,840,841,844,847],{"class":167,"line":168},[165,842,843],{"class":391},"interface",[165,845,846],{"class":171}," LayoutProps",[165,848,395],{"class":307},[165,850,851,854,857],{"class":167,"line":205},[165,852,853],{"class":423},"  body",[165,855,856],{"class":307},":",[165,858,859],{"class":171}," string\n",[165,861,862,865,867,870],{"class":167,"line":366},[165,863,864],{"class":423},"  head",[165,866,856],{"class":307},[165,868,869],{"class":171}," string",[165,871,872],{"class":311},"[]\n",[165,874,875],{"class":167,"line":373},[165,876,877],{"class":307},"}\n",[165,879,880],{"class":167,"line":398},[165,881,370],{"emptyLinePlaceholder":369},[165,883,884,886,889,891,893,897,899,901,904],{"class":167,"line":417},[165,885,376],{"class":303},[165,887,888],{"class":391}," function",[165,890,351],{"class":199},[165,892,385],{"class":307},[165,894,896],{"class":895},"sHdIc","props",[165,898,856],{"class":307},[165,900,846],{"class":171},[165,902,903],{"class":307},")",[165,905,395],{"class":307},[165,907,908,910,912,914,916,918,920,922],{"class":167,"line":427},[165,909,401],{"class":391},[165,911,308],{"class":307},[165,913,693],{"class":311},[165,915,315],{"class":307},[165,917,688],{"class":311},[165,919,326],{"class":307},[165,921,407],{"class":307},[165,923,924],{"class":311}," props\n",[165,926,927],{"class":167,"line":439},[165,928,370],{"emptyLinePlaceholder":369},[165,930,931,933,936,939],{"class":167,"line":450},[165,932,769],{"class":303},[165,934,935],{"class":341}," \u002F* html *\u002F",[165,937,938],{"class":307},"`",[165,940,941],{"class":175},"\u003Chtml>\n",[165,943,944],{"class":167,"line":472},[165,945,946],{"class":175},"      \u003Chead>\n",[165,948,949],{"class":167,"line":482},[165,950,951],{"class":175},"       \u003Clink\n",[165,953,954],{"class":167,"line":500},[165,955,956],{"class":175},"          rel=\"stylesheet\"\n",[165,958,959],{"class":167,"line":536},[165,960,961],{"class":175},"          href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@picocss\u002Fpico@2\u002Fcss\u002Fpico.min.css\"\n",[165,963,964],{"class":167,"line":594},[165,965,966],{"class":175},"        \u002F>\n",[165,968,969,972,975,977,980,982,984,987,989,991],{"class":167,"line":624},[165,970,971],{"class":307},"        ${",[165,973,974],{"class":311},"head",[165,976,35],{"class":307},[165,978,979],{"class":199},"join",[165,981,385],{"class":311},[165,983,338],{"class":307},[165,985,986],{"class":311},"\\n",[165,988,338],{"class":307},[165,990,903],{"class":311},[165,992,877],{"class":307},[165,994,995],{"class":167,"line":633},[165,996,997],{"class":175},"      \u003C\u002Fhead>\n",[165,999,1000],{"class":167,"line":643},[165,1001,1002],{"class":175},"      \u003Cbody>\n",[165,1004,1005],{"class":167,"line":649},[165,1006,1007],{"class":175},"        \u003Cheader class=\"container\">\n",[165,1009,1010],{"class":167,"line":655},[165,1011,1012],{"class":175},"          \u003Ch1>\n",[165,1014,1015],{"class":167,"line":681},[165,1016,1017],{"class":175},"            \u003Ca href=\"\u002F\">Raccourcisseur d'URL avec Nitro\u003C\u002Fa>\n",[165,1019,1020],{"class":167,"line":714},[165,1021,1022],{"class":175},"          \u003C\u002Fh1>\n",[165,1024,1025],{"class":167,"line":719},[165,1026,1027],{"class":175},"        \u003C\u002Fheader>\n",[165,1029,1030],{"class":167,"line":736},[165,1031,1032],{"class":175},"        \u003Cmain class=\"container\">\n",[165,1034,1035,1038,1041],{"class":167,"line":745},[165,1036,1037],{"class":307},"          ${",[165,1039,1040],{"class":311},"body",[165,1042,877],{"class":307},[165,1044,1045],{"class":167,"line":753},[165,1046,1047],{"class":175},"        \u003C\u002Fmain>\n",[165,1049,1050],{"class":167,"line":761},[165,1051,1052],{"class":175},"      \u003C\u002Fbody>\n",[165,1054,1055,1058],{"class":167,"line":766},[165,1056,1057],{"class":175},"    \u003C\u002Fhtml>",[165,1059,1060],{"class":307},"`\n",[165,1062,1063],{"class":167,"line":783},[165,1064,877],{"class":307},[10,1066,1067,1068,1071,1072,35],{},"Ceci est un simple ",[14,1069,1070],{},"template de chaîne"," où nous incorporons le contenu de l'en-tête et du corps générés par ",[14,1073,335],{},[268,1075,1077],{"id":1076},"stockage-de-lurl","Stockage de l'URL",[10,1079,1080,1081,1084],{},"Avant de procéder, installez ",[162,1082,1083],{},"zod"," pour valider le corps de la requête.",[155,1086,1088],{"className":157,"code":1087,"language":159,"meta":160,"style":160},"npm install zod\n",[162,1089,1090],{"__ignoreMap":160},[165,1091,1092,1094,1096],{"class":167,"line":168},[165,1093,208],{"class":171},[165,1095,260],{"class":175},[165,1097,1098],{"class":175}," zod\n",[10,1100,1101,1102,1104,1105,1107,1108,1110,1111,1114,1115,35],{},"Créez la route ",[162,1103,517],{}," pour gérer la ",[14,1106,809],{}," et stocker l'URL dans le ",[14,1109,63],{},". Cette route est étiquetée ",[162,1112,1113],{},"create.post.tsx"," et se trouve dans le répertoire ",[162,1116,285],{},[155,1118,1121],{"className":293,"code":1119,"filename":1120,"language":296,"meta":160,"style":160},"import { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod' \u002F\u002F le `h` est essentiel ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, z.object({\n    url: z.string().url(),\n  }).parse)\n\n  const requestURL = getRequestURL(event)\n  const id = hash(body.url)\n  const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n  await useStorage('data').setItem(id, body.url)\n\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Créé\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Créé et prêt\u003C\u002Fh2>\n        \u003Cinput\n          type=\"text\"\n          value={shortenURL}\n          autofocus\n        \u002F>\n      \u003C\u002Fdiv>\n    )\n  }\n\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body: nanoBody, head } = Helmet.SSR(app)\n\n  return withTemplate({\n    body: nanoBody,\n    head,\n  })\n})\n","server\u002Froutes\u002Fcreate.post.tsx",[162,1122,1123,1149,1168,1190,1208,1212,1237,1268,1292,1305,1309,1327,1348,1390,1394,1432,1436,1450,1456,1464,1472,1489,1497,1514,1521,1536,1549,1554,1558,1566,1570,1575,1580,1601,1635,1640,1651,1662,1669,1676],{"__ignoreMap":160},[165,1124,1125,1127,1129,1131,1133,1135,1137,1139,1141,1143,1145,1147],{"class":167,"line":168},[165,1126,304],{"class":303},[165,1128,308],{"class":307},[165,1130,312],{"class":311},[165,1132,315],{"class":307},[165,1134,318],{"class":311},[165,1136,315],{"class":307},[165,1138,323],{"class":311},[165,1140,326],{"class":307},[165,1142,329],{"class":303},[165,1144,332],{"class":307},[165,1146,335],{"class":175},[165,1148,363],{"class":307},[165,1150,1151,1153,1155,1158,1160,1162,1164,1166],{"class":167,"line":205},[165,1152,304],{"class":303},[165,1154,308],{"class":307},[165,1156,1157],{"class":311}," hash",[165,1159,326],{"class":307},[165,1161,329],{"class":303},[165,1163,332],{"class":307},[165,1165,110],{"class":175},[165,1167,363],{"class":307},[165,1169,1170,1172,1174,1177,1179,1181,1183,1185,1187],{"class":167,"line":366},[165,1171,304],{"class":303},[165,1173,308],{"class":307},[165,1175,1176],{"class":311}," z",[165,1178,326],{"class":307},[165,1180,329],{"class":303},[165,1182,332],{"class":307},[165,1184,1083],{"class":175},[165,1186,338],{"class":307},[165,1188,1189],{"class":341}," \u002F\u002F le `h` est essentiel ici\n",[165,1191,1192,1194,1196,1198,1200,1202,1204,1206],{"class":167,"line":373},[165,1193,304],{"class":303},[165,1195,308],{"class":307},[165,1197,351],{"class":311},[165,1199,326],{"class":307},[165,1201,329],{"class":303},[165,1203,332],{"class":307},[165,1205,360],{"class":175},[165,1207,363],{"class":307},[165,1209,1210],{"class":167,"line":398},[165,1211,370],{"emptyLinePlaceholder":369},[165,1213,1214,1216,1218,1220,1222,1225,1228,1231,1233,1235],{"class":167,"line":417},[165,1215,376],{"class":303},[165,1217,379],{"class":303},[165,1219,772],{"class":199},[165,1221,385],{"class":311},[165,1223,1224],{"class":391},"async",[165,1226,1227],{"class":307}," (",[165,1229,1230],{"class":895},"event",[165,1232,903],{"class":307},[165,1234,392],{"class":391},[165,1236,395],{"class":307},[165,1238,1239,1241,1243,1245,1248,1251,1253,1255,1257,1259,1261,1264,1266],{"class":167,"line":427},[165,1240,401],{"class":391},[165,1242,688],{"class":311},[165,1244,407],{"class":307},[165,1246,1247],{"class":303}," await",[165,1249,1250],{"class":199}," readValidatedBody",[165,1252,385],{"class":423},[165,1254,1230],{"class":311},[165,1256,315],{"class":307},[165,1258,1176],{"class":311},[165,1260,35],{"class":307},[165,1262,1263],{"class":199},"object",[165,1265,385],{"class":423},[165,1267,733],{"class":307},[165,1269,1270,1273,1275,1277,1279,1282,1284,1286,1288,1290],{"class":167,"line":439},[165,1271,1272],{"class":423},"    url",[165,1274,856],{"class":307},[165,1276,1176],{"class":311},[165,1278,35],{"class":307},[165,1280,1281],{"class":199},"string",[165,1283,388],{"class":423},[165,1285,35],{"class":307},[165,1287,551],{"class":199},[165,1289,388],{"class":423},[165,1291,742],{"class":307},[165,1293,1294,1296,1298,1300,1303],{"class":167,"line":450},[165,1295,756],{"class":307},[165,1297,903],{"class":423},[165,1299,35],{"class":307},[165,1301,1302],{"class":311},"parse",[165,1304,678],{"class":423},[165,1306,1307],{"class":167,"line":472},[165,1308,370],{"emptyLinePlaceholder":369},[165,1310,1311,1313,1316,1318,1321,1323,1325],{"class":167,"line":482},[165,1312,401],{"class":391},[165,1314,1315],{"class":311}," requestURL",[165,1317,407],{"class":307},[165,1319,1320],{"class":199}," getRequestURL",[165,1322,385],{"class":423},[165,1324,1230],{"class":311},[165,1326,678],{"class":423},[165,1328,1329,1331,1334,1336,1338,1340,1342,1344,1346],{"class":167,"line":500},[165,1330,401],{"class":391},[165,1332,1333],{"class":311}," id",[165,1335,407],{"class":307},[165,1337,1157],{"class":199},[165,1339,385],{"class":423},[165,1341,1040],{"class":311},[165,1343,35],{"class":307},[165,1345,551],{"class":311},[165,1347,678],{"class":423},[165,1349,1350,1352,1355,1357,1360,1363,1365,1367,1370,1373,1376,1379,1381,1383,1385,1387],{"class":167,"line":536},[165,1351,401],{"class":391},[165,1353,1354],{"class":311}," shortenURL",[165,1356,407],{"class":307},[165,1358,1359],{"class":307}," new",[165,1361,1362],{"class":199}," URL",[165,1364,385],{"class":423},[165,1366,938],{"class":307},[165,1368,1369],{"class":175},"\u002F",[165,1371,1372],{"class":307},"${",[165,1374,1375],{"class":311},"id",[165,1377,1378],{"class":307},"}`",[165,1380,315],{"class":307},[165,1382,1315],{"class":311},[165,1384,903],{"class":423},[165,1386,35],{"class":307},[165,1388,1389],{"class":311},"href\n",[165,1391,1392],{"class":167,"line":594},[165,1393,370],{"emptyLinePlaceholder":369},[165,1395,1396,1399,1402,1404,1406,1409,1411,1413,1415,1418,1420,1422,1424,1426,1428,1430],{"class":167,"line":624},[165,1397,1398],{"class":303},"  await",[165,1400,1401],{"class":199}," useStorage",[165,1403,385],{"class":423},[165,1405,338],{"class":307},[165,1407,1408],{"class":175},"data",[165,1410,338],{"class":307},[165,1412,903],{"class":423},[165,1414,35],{"class":307},[165,1416,1417],{"class":199},"setItem",[165,1419,385],{"class":423},[165,1421,1375],{"class":311},[165,1423,315],{"class":307},[165,1425,688],{"class":311},[165,1427,35],{"class":307},[165,1429,551],{"class":311},[165,1431,678],{"class":423},[165,1433,1434],{"class":167,"line":633},[165,1435,370],{"emptyLinePlaceholder":369},[165,1437,1438,1440,1442,1444,1446,1448],{"class":167,"line":643},[165,1439,401],{"class":391},[165,1441,404],{"class":311},[165,1443,407],{"class":307},[165,1445,410],{"class":307},[165,1447,392],{"class":391},[165,1449,395],{"class":307},[165,1451,1452,1454],{"class":167,"line":649},[165,1453,420],{"class":303},[165,1455,424],{"class":423},[165,1457,1458,1460,1462],{"class":167,"line":655},[165,1459,430],{"class":307},[165,1461,433],{"class":423},[165,1463,436],{"class":307},[165,1465,1466,1468,1470],{"class":167,"line":681},[165,1467,442],{"class":307},[165,1469,445],{"class":171},[165,1471,436],{"class":307},[165,1473,1474,1476,1478,1480,1483,1485,1487],{"class":167,"line":714},[165,1475,453],{"class":307},[165,1477,456],{"class":423},[165,1479,459],{"class":307},[165,1481,1482],{"class":311},"Créé",[165,1484,465],{"class":307},[165,1486,456],{"class":423},[165,1488,436],{"class":307},[165,1490,1491,1493,1495],{"class":167,"line":719},[165,1492,475],{"class":307},[165,1494,445],{"class":171},[165,1496,436],{"class":307},[165,1498,1499,1501,1503,1505,1508,1510,1512],{"class":167,"line":736},[165,1500,442],{"class":307},[165,1502,143],{"class":423},[165,1504,459],{"class":307},[165,1506,1507],{"class":311},"Créé et prêt",[165,1509,465],{"class":307},[165,1511,143],{"class":423},[165,1513,436],{"class":307},[165,1515,1516,1518],{"class":167,"line":745},[165,1517,442],{"class":307},[165,1519,1520],{"class":423},"input\n",[165,1522,1523,1526,1528,1530,1533],{"class":167,"line":753},[165,1524,1525],{"class":391},"          type",[165,1527,511],{"class":307},[165,1529,514],{"class":307},[165,1531,1532],{"class":175},"text",[165,1534,1535],{"class":307},"\"\n",[165,1537,1538,1541,1544,1547],{"class":167,"line":761},[165,1539,1540],{"class":391},"          value",[165,1542,1543],{"class":307},"={",[165,1545,1546],{"class":311},"shortenURL",[165,1548,877],{"class":307},[165,1550,1551],{"class":167,"line":766},[165,1552,1553],{"class":391},"          autofocus\n",[165,1555,1556],{"class":167,"line":783},[165,1557,966],{"class":307},[165,1559,1560,1562,1564],{"class":167,"line":791},[165,1561,636],{"class":307},[165,1563,433],{"class":423},[165,1565,436],{"class":307},[165,1567,1568],{"class":167,"line":798},[165,1569,646],{"class":423},[165,1571,1573],{"class":167,"line":1572},31,[165,1574,652],{"class":307},[165,1576,1578],{"class":167,"line":1577},32,[165,1579,370],{"emptyLinePlaceholder":369},[165,1581,1583,1585,1587,1589,1591,1593,1595,1597,1599],{"class":167,"line":1582},33,[165,1584,401],{"class":391},[165,1586,660],{"class":311},[165,1588,407],{"class":307},[165,1590,323],{"class":199},[165,1592,385],{"class":423},[165,1594,669],{"class":307},[165,1596,672],{"class":171},[165,1598,675],{"class":307},[165,1600,678],{"class":423},[165,1602,1604,1606,1608,1610,1612,1615,1617,1619,1621,1623,1625,1627,1629,1631,1633],{"class":167,"line":1603},34,[165,1605,401],{"class":391},[165,1607,308],{"class":307},[165,1609,688],{"class":423},[165,1611,856],{"class":307},[165,1613,1614],{"class":311}," nanoBody",[165,1616,315],{"class":307},[165,1618,693],{"class":311},[165,1620,326],{"class":307},[165,1622,407],{"class":307},[165,1624,318],{"class":311},[165,1626,35],{"class":307},[165,1628,704],{"class":199},[165,1630,385],{"class":423},[165,1632,709],{"class":311},[165,1634,678],{"class":423},[165,1636,1638],{"class":167,"line":1637},35,[165,1639,370],{"emptyLinePlaceholder":369},[165,1641,1643,1645,1647,1649],{"class":167,"line":1642},36,[165,1644,769],{"class":303},[165,1646,351],{"class":199},[165,1648,385],{"class":423},[165,1650,733],{"class":307},[165,1652,1654,1656,1658,1660],{"class":167,"line":1653},37,[165,1655,739],{"class":423},[165,1657,856],{"class":307},[165,1659,1614],{"class":311},[165,1661,742],{"class":307},[165,1663,1665,1667],{"class":167,"line":1664},38,[165,1666,748],{"class":311},[165,1668,742],{"class":307},[165,1670,1672,1674],{"class":167,"line":1671},39,[165,1673,756],{"class":307},[165,1675,678],{"class":423},[165,1677,1679,1681],{"class":167,"line":1678},40,[165,1680,801],{"class":307},[165,1682,678],{"class":311},[10,1684,1685,1686,1689,1690,1693,1694,1696],{},"Dans ce segment, nous utilisons la fonction ",[162,1687,1688],{},"readValidatedBody"," pour ",[14,1691,1692],{},"valider le corps de la requête",". Cela assure que le champ ",[162,1695,551],{}," est une URL légitime, sinon une erreur est générée.",[10,1698,1699,1700,1703,1704,1707,1708,35],{},"Nous récupérons l'",[14,1701,1702],{},"URL de la requête"," en utilisant la fonction ",[162,1705,1706],{},"getRequestURL"," de ",[162,1709,268],{},[10,1711,1712,1713,1715,1716,1707,1718,1720],{},"Le ",[14,1714,114],{}," est créé à partir de l'URL du corps en utilisant la fonction ",[162,1717,114],{},[14,1719,110],{},", garantissant une génération de hash cohérente pour éviter les collisions.",[10,1722,1723,1724,1703,1726,1707,1729,1731,1732,1734],{},"L'URL est stockée dans le ",[14,1725,63],{},[162,1727,1728],{},"useStorage",[14,1730,94],{},", employant le namespace ",[162,1733,1408],{}," pour stocker les URL, préconfiguré pour notre utilisation.",[1736,1737,1740,1743,1750,1851,1873],"callout-read-more",{"text":1738,"href":1739},"Stockage KV","https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fstorage#usage",[10,1741,1742],{},"À la fin de ce gestionnaire d'événements, une route est renvoyée avec l'URL raccourcie que les utilisateurs peuvent copier et utiliser.",[10,1744,1745,1746,1749],{},"Pour le développement, tout fonctionne correctement, mais nous devons mettre à jour cette configuration, ",[162,1747,1748],{},"nitro.config.ts",", pour l'environnement de production.",[155,1751,1753],{"className":833,"code":1752,"filename":1748,"language":836,"meta":160,"style":160},"export default defineNitroConfig({\n  srcDir: 'server',\n  $production: {\n    storage: { data: { driver: 'cloudflare-kv-binding', binding: 'url-shortener' } },\n  },\n})\n",[162,1754,1755,1768,1784,1793,1840,1845],{"__ignoreMap":160},[165,1756,1757,1759,1761,1764,1766],{"class":167,"line":168},[165,1758,376],{"class":303},[165,1760,379],{"class":303},[165,1762,1763],{"class":199}," defineNitroConfig",[165,1765,385],{"class":311},[165,1767,733],{"class":307},[165,1769,1770,1773,1775,1777,1780,1782],{"class":167,"line":205},[165,1771,1772],{"class":423},"  srcDir",[165,1774,856],{"class":307},[165,1776,332],{"class":307},[165,1778,1779],{"class":175},"server",[165,1781,338],{"class":307},[165,1783,742],{"class":307},[165,1785,1786,1789,1791],{"class":167,"line":366},[165,1787,1788],{"class":423},"  $production",[165,1790,856],{"class":307},[165,1792,395],{"class":307},[165,1794,1795,1798,1800,1802,1805,1807,1809,1812,1814,1816,1819,1821,1823,1826,1828,1830,1833,1835,1837],{"class":167,"line":373},[165,1796,1797],{"class":423},"    storage",[165,1799,856],{"class":307},[165,1801,308],{"class":307},[165,1803,1804],{"class":423}," data",[165,1806,856],{"class":307},[165,1808,308],{"class":307},[165,1810,1811],{"class":423}," driver",[165,1813,856],{"class":307},[165,1815,332],{"class":307},[165,1817,1818],{"class":175},"cloudflare-kv-binding",[165,1820,338],{"class":307},[165,1822,315],{"class":307},[165,1824,1825],{"class":423}," binding",[165,1827,856],{"class":307},[165,1829,332],{"class":307},[165,1831,1832],{"class":175},"url-shortener",[165,1834,338],{"class":307},[165,1836,326],{"class":307},[165,1838,1839],{"class":307}," },\n",[165,1841,1842],{"class":167,"line":398},[165,1843,1844],{"class":307},"  },\n",[165,1846,1847,1849],{"class":167,"line":417},[165,1848,801],{"class":307},[165,1850,678],{"class":311},[10,1852,1853,1854,1856,1857,1859,1860,1862,1863,1866,1867,1870,1871,35],{},"Dans cette configuration, nous définissons le namespace ",[162,1855,1408],{},", comme dans le développement, afin d'utiliser le driver ",[14,1858,1818],{}," et le binding ",[162,1861,1832],{},", spécifié sous la clé ",[162,1864,1865],{},"$production",". Cela indique que la configuration s'applique uniquement à l'",[14,1868,1869],{},"environnement de production"," pour accéder au ",[14,1872,73],{},[1736,1874,1877,1881,1890,2089,2109,2113,2123],{"text":1875,"href":1876},"Configuration spécifique à l'environnement","https:\u002F\u002Fgithub.com\u002Funjs\u002Fc12#environment-specific-configuration",[268,1878,1880],{"id":1879},"redirection-vers-lurl-originale","Redirection vers l'URL Originale",[10,1882,1883,1884,1887,1888,35],{},"Enfin, nous devons établir une route pour gérer les URL raccourcies et rediriger les utilisateurs vers l'URL principale. Cette route est nommée ",[162,1885,1886],{},"[id].get.ts"," et est située dans le répertoire ",[162,1889,285],{},[155,1891,1894],{"className":833,"code":1892,"filename":1893,"language":836,"meta":160,"style":160},"export default defineEventHandler(async (event) => {\n  const id = getRouterParam(event, 'short')\n\n  const value = await useStorage\u003Cstring>('data').getItem(id)\n\n  if (!value) {\n    throw createError({\n      statusCode: 404,\n      statusMessage: 'Non trouvé',\n    })\n  }\n\n  return sendRedirect(event, value)\n})\n","server\u002Froutes\u002F[id].get.ts",[162,1895,1896,1918,1944,1948,1988,1992,2010,2022,2035,2051,2058,2062,2066,2083],{"__ignoreMap":160},[165,1897,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916],{"class":167,"line":168},[165,1899,376],{"class":303},[165,1901,379],{"class":303},[165,1903,772],{"class":199},[165,1905,385],{"class":311},[165,1907,1224],{"class":391},[165,1909,1227],{"class":307},[165,1911,1230],{"class":895},[165,1913,903],{"class":307},[165,1915,392],{"class":391},[165,1917,395],{"class":307},[165,1919,1920,1922,1924,1926,1929,1931,1933,1935,1937,1940,1942],{"class":167,"line":205},[165,1921,401],{"class":391},[165,1923,1333],{"class":311},[165,1925,407],{"class":307},[165,1927,1928],{"class":199}," getRouterParam",[165,1930,385],{"class":423},[165,1932,1230],{"class":311},[165,1934,315],{"class":307},[165,1936,332],{"class":307},[165,1938,1939],{"class":175},"short",[165,1941,338],{"class":307},[165,1943,678],{"class":423},[165,1945,1946],{"class":167,"line":366},[165,1947,370],{"emptyLinePlaceholder":369},[165,1949,1950,1952,1955,1957,1959,1961,1963,1965,1967,1969,1971,1973,1975,1977,1979,1982,1984,1986],{"class":167,"line":373},[165,1951,401],{"class":391},[165,1953,1954],{"class":311}," value",[165,1956,407],{"class":307},[165,1958,1247],{"class":303},[165,1960,1401],{"class":199},[165,1962,669],{"class":307},[165,1964,1281],{"class":171},[165,1966,459],{"class":307},[165,1968,385],{"class":423},[165,1970,338],{"class":307},[165,1972,1408],{"class":175},[165,1974,338],{"class":307},[165,1976,903],{"class":423},[165,1978,35],{"class":307},[165,1980,1981],{"class":199},"getItem",[165,1983,385],{"class":423},[165,1985,1375],{"class":311},[165,1987,678],{"class":423},[165,1989,1990],{"class":167,"line":398},[165,1991,370],{"emptyLinePlaceholder":369},[165,1993,1994,1997,1999,2002,2005,2008],{"class":167,"line":417},[165,1995,1996],{"class":303},"  if",[165,1998,1227],{"class":423},[165,2000,2001],{"class":307},"!",[165,2003,2004],{"class":311},"value",[165,2006,2007],{"class":423},") ",[165,2009,733],{"class":307},[165,2011,2012,2015,2018,2020],{"class":167,"line":427},[165,2013,2014],{"class":303},"    throw",[165,2016,2017],{"class":199}," createError",[165,2019,385],{"class":423},[165,2021,733],{"class":307},[165,2023,2024,2027,2029,2033],{"class":167,"line":439},[165,2025,2026],{"class":423},"      statusCode",[165,2028,856],{"class":307},[165,2030,2032],{"class":2031},"sbssI"," 404",[165,2034,742],{"class":307},[165,2036,2037,2040,2042,2044,2047,2049],{"class":167,"line":450},[165,2038,2039],{"class":423},"      statusMessage",[165,2041,856],{"class":307},[165,2043,332],{"class":307},[165,2045,2046],{"class":175},"Non trouvé",[165,2048,338],{"class":307},[165,2050,742],{"class":307},[165,2052,2053,2056],{"class":167,"line":472},[165,2054,2055],{"class":307},"    }",[165,2057,678],{"class":423},[165,2059,2060],{"class":167,"line":482},[165,2061,652],{"class":307},[165,2063,2064],{"class":167,"line":500},[165,2065,370],{"emptyLinePlaceholder":369},[165,2067,2068,2070,2073,2075,2077,2079,2081],{"class":167,"line":536},[165,2069,769],{"class":303},[165,2071,2072],{"class":199}," sendRedirect",[165,2074,385],{"class":423},[165,2076,1230],{"class":311},[165,2078,315],{"class":307},[165,2080,1954],{"class":311},[165,2082,678],{"class":423},[165,2084,2085,2087],{"class":167,"line":594},[165,2086,801],{"class":307},[165,2088,678],{"class":311},[10,2090,2091,2092,2094,2095,2097,2098,2101,2102,2104,2105,2108],{},"Ici, nous obtenons simplement l'",[162,2093,1375],{}," à partir des paramètres du routeur et utilisons ",[162,2096,1728],{}," pour récupérer l'",[14,2099,2100],{},"URL"," du ",[14,2103,63],{},". Si l'URL n'existe pas, une ",[14,2106,2107],{},"erreur 404"," est générée. Sinon, l'utilisateur est redirigé vers l'URL originale.",[143,2110,2112],{"id":2111},"déployer-le-raccourcisseur-durl","Déployer le Raccourcisseur d'URL",[10,2114,2115,2116,2119,2120,35],{},"Félicitations ! Nous avons réussi à créer un raccourcisseur d'URL de base en utilisant Nitro. La prochaine étape consiste à ",[14,2117,2118],{},"le déployer"," sur Cloudflare Pages. Rassurez-vous, c'est simple grâce aux ",[14,2121,2122],{},"fournisseurs de déploiement sans configuration",[1736,2124,2127,2136,2155,2160,2177,2192,2199,2203,2213,2983,2990,3013,3017,3031,3039],{"text":2125,"href":2126},"Fournisseurs Zero Config","https:\u002F\u002Fnitro.unjs.io\u002Fdeploy#zero-config-providers",[2128,2129,2130],"blockquote",{},[10,2131,2132,2135],{},[165,2133,2134],{},"!WARNING","\nUn compte Cloudflare et un dépôt GitHub sont nécessaires pour continuer avec le déploiement.\nTout d'abord, connectez-vous au tableau de bord Cloudflare et sélectionnez un compte. Naviguez vers Accueil du compte, choisissez Workers & Pages, et créez une nouvelle application en utilisant le bouton en haut à droite. Sélectionnez l'onglet Pages et suivez les instructions pour établir une connexion GitHub.",[10,2137,2138,2139,2142,2143,2146,2147,2150,2151,2154],{},"Sélectionnez le dépôt, ajoutez la commande de construction ",[162,2140,2141],{},"npm run build",", spécifiez le répertoire de sortie ",[162,2144,2145],{},"dist",", incluez une variable d'environnement ",[162,2148,2149],{},"NODE_ENV"," avec la valeur ",[162,2152,2153],{},"production",", puis enregistrez et déployez.",[10,2156,2157,2158,35],{},"Maintenant, attendons que le déploiement soit terminé. Une fois terminé, vous pouvez accéder au raccourcisseur d'URL en utilisant l'URL fournie par ",[14,2159,34],{},[10,2161,2162,2165,2166,2169,2170,2173,2174,2176],{},[14,2163,2164],{},"Cependant",", il se peut qu'il ne fonctionne pas adéquatement initialement, car un ",[14,2167,2168],{},"namespace KV"," n'a pas été lié. Cela nécessite un lien manuel. Allez dans les paramètres du projet, sélectionnez les fonctions et faites défiler jusqu'à ce que vous trouviez ",[14,2171,2172],{},"liaisons de namespace KV",". Ajoutez une liaison avec le nom ",[162,2175,1832],{}," et choisissez le namespace désiré.",[2128,2178,2179],{},[10,2180,2181,2184,2185,2187,2188,2191],{},[165,2182,2183],{},"!NOTE","\nCréer un ",[14,2186,2168],{}," est réalisable dans la section ",[14,2189,2190],{},"Workers & Pages",".\nRedéployez votre application pour tenir compte de la nouvelle liaison de namespace KV. Une fois terminé, le raccourcisseur d'URL sera pleinement fonctionnel. ✨",[2128,2193,2194],{},[10,2195,2196,2198],{},[165,2197,2183],{},"\nSi vous n'avez pas l'intention de maintenir le projet, n'oubliez pas de le supprimer du tableau de bord Cloudflare Pages afin d'éviter des dépenses inutiles.",[143,2200,2202],{"id":2201},"améliorations-et-considérations-futures","Améliorations et Considérations Futures",[10,2204,2205,2206,2209,2210,2212],{},"Pour améliorer la sécurité, envisagez d'ajouter une simple ",[14,2207,2208],{},"protection CSRF"," en utilisant la syntaxe des objets gestionnaires. Modifiez le fichier ",[162,2211,1113],{}," pour inclure un gestionnaire avant la requête pour la vérification de l'en-tête d'origine.",[155,2214,2216],{"className":293,"code":2215,"filename":1120,"language":296,"meta":160,"style":160},"import { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod'\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler({\n  onBeforeResponse: async (event) => {\n    const requestURL = getRequestURL(event).origin\n    const origin = getRequestHeader(event, 'origin')\n\n    if (!origin) {\n      throw createError({\n        statusCode: 400,\n        statusMessage: 'Mauvaise requête',\n      })\n    }\n\n    if (origin !== requestURL) {\n      throw createError({\n        statusCode: 403,\n        statusMessage: 'Interdit',\n      })\n    }\n  },\n  handler: async (event) => {\n    const body = await readValidatedBody(event, z.object({\n      url: z.string().url(),\n    }).parse)\n\n    const requestURL = getRequestURL(event)\n    const id = hash(body.url)\n    const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n    await useStorage('data').setItem(id, body.url)\n\n    const App = () => {\n      return (\n        \u003Cdiv>\n          \u003CHelmet>\n            \u003Ctitle>Créé\u003C\u002Ftitle>\n          \u003C\u002FHelmet>\n          \u003Ch2>Créé et prêt\u003C\u002Fh2>\n          \u003Cinput\n            type=\"text\"\n            value={shortenURL}\n            autofocus\n          \u002F>\n        \u003C\u002Fdiv>\n      )\n    }\n\n    const app = renderSSR(\u003CApp \u002F>)\n    const { body: nanoBody, head } = Helmet.SSR(app)\n\n    return withTemplate({\n      body: nanoBody,\n      head,\n    })\n  },\n})\n",[162,2217,2218,2244,2262,2280,2298,2302,2314,2334,2356,2383,2387,2402,2413,2425,2441,2448,2453,2457,2474,2484,2495,2510,2516,2520,2524,2543,2571,2594,2606,2610,2626,2646,2680,2684,2719,2723,2737,2744,2752,2760,2777,2787,2804,2811,2825,2837,2843,2849,2858,2864,2869,2874,2895,2928,2933,2944,2956,2964,2971,2976],{"__ignoreMap":160},[165,2219,2220,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240,2242],{"class":167,"line":168},[165,2221,304],{"class":303},[165,2223,308],{"class":307},[165,2225,312],{"class":311},[165,2227,315],{"class":307},[165,2229,318],{"class":311},[165,2231,315],{"class":307},[165,2233,323],{"class":311},[165,2235,326],{"class":307},[165,2237,329],{"class":303},[165,2239,332],{"class":307},[165,2241,335],{"class":175},[165,2243,363],{"class":307},[165,2245,2246,2248,2250,2252,2254,2256,2258,2260],{"class":167,"line":205},[165,2247,304],{"class":303},[165,2249,308],{"class":307},[165,2251,1157],{"class":311},[165,2253,326],{"class":307},[165,2255,329],{"class":303},[165,2257,332],{"class":307},[165,2259,110],{"class":175},[165,2261,363],{"class":307},[165,2263,2264,2266,2268,2270,2272,2274,2276,2278],{"class":167,"line":366},[165,2265,304],{"class":303},[165,2267,308],{"class":307},[165,2269,1176],{"class":311},[165,2271,326],{"class":307},[165,2273,329],{"class":303},[165,2275,332],{"class":307},[165,2277,1083],{"class":175},[165,2279,363],{"class":307},[165,2281,2282,2284,2286,2288,2290,2292,2294,2296],{"class":167,"line":373},[165,2283,304],{"class":303},[165,2285,308],{"class":307},[165,2287,351],{"class":311},[165,2289,326],{"class":307},[165,2291,329],{"class":303},[165,2293,332],{"class":307},[165,2295,360],{"class":175},[165,2297,363],{"class":307},[165,2299,2300],{"class":167,"line":398},[165,2301,370],{"emptyLinePlaceholder":369},[165,2303,2304,2306,2308,2310,2312],{"class":167,"line":417},[165,2305,376],{"class":303},[165,2307,379],{"class":303},[165,2309,772],{"class":199},[165,2311,385],{"class":311},[165,2313,733],{"class":307},[165,2315,2316,2319,2321,2324,2326,2328,2330,2332],{"class":167,"line":427},[165,2317,2318],{"class":199},"  onBeforeResponse",[165,2320,856],{"class":307},[165,2322,2323],{"class":391}," async",[165,2325,1227],{"class":307},[165,2327,1230],{"class":895},[165,2329,903],{"class":307},[165,2331,392],{"class":391},[165,2333,395],{"class":307},[165,2335,2336,2339,2341,2343,2345,2347,2349,2351,2353],{"class":167,"line":439},[165,2337,2338],{"class":391},"    const",[165,2340,1315],{"class":311},[165,2342,407],{"class":307},[165,2344,1320],{"class":199},[165,2346,385],{"class":423},[165,2348,1230],{"class":311},[165,2350,903],{"class":423},[165,2352,35],{"class":307},[165,2354,2355],{"class":311},"origin\n",[165,2357,2358,2360,2363,2365,2368,2370,2372,2374,2376,2379,2381],{"class":167,"line":450},[165,2359,2338],{"class":391},[165,2361,2362],{"class":311}," origin",[165,2364,407],{"class":307},[165,2366,2367],{"class":199}," getRequestHeader",[165,2369,385],{"class":423},[165,2371,1230],{"class":311},[165,2373,315],{"class":307},[165,2375,332],{"class":307},[165,2377,2378],{"class":175},"origin",[165,2380,338],{"class":307},[165,2382,678],{"class":423},[165,2384,2385],{"class":167,"line":472},[165,2386,370],{"emptyLinePlaceholder":369},[165,2388,2389,2392,2394,2396,2398,2400],{"class":167,"line":482},[165,2390,2391],{"class":303},"    if",[165,2393,1227],{"class":423},[165,2395,2001],{"class":307},[165,2397,2378],{"class":311},[165,2399,2007],{"class":423},[165,2401,733],{"class":307},[165,2403,2404,2407,2409,2411],{"class":167,"line":500},[165,2405,2406],{"class":303},"      throw",[165,2408,2017],{"class":199},[165,2410,385],{"class":423},[165,2412,733],{"class":307},[165,2414,2415,2418,2420,2423],{"class":167,"line":536},[165,2416,2417],{"class":423},"        statusCode",[165,2419,856],{"class":307},[165,2421,2422],{"class":2031}," 400",[165,2424,742],{"class":307},[165,2426,2427,2430,2432,2434,2437,2439],{"class":167,"line":594},[165,2428,2429],{"class":423},"        statusMessage",[165,2431,856],{"class":307},[165,2433,332],{"class":307},[165,2435,2436],{"class":175},"Mauvaise requête",[165,2438,338],{"class":307},[165,2440,742],{"class":307},[165,2442,2443,2446],{"class":167,"line":624},[165,2444,2445],{"class":307},"      }",[165,2447,678],{"class":423},[165,2449,2450],{"class":167,"line":633},[165,2451,2452],{"class":307},"    }\n",[165,2454,2455],{"class":167,"line":643},[165,2456,370],{"emptyLinePlaceholder":369},[165,2458,2459,2461,2463,2465,2468,2470,2472],{"class":167,"line":649},[165,2460,2391],{"class":303},[165,2462,1227],{"class":423},[165,2464,2378],{"class":311},[165,2466,2467],{"class":307}," !==",[165,2469,1315],{"class":311},[165,2471,2007],{"class":423},[165,2473,733],{"class":307},[165,2475,2476,2478,2480,2482],{"class":167,"line":655},[165,2477,2406],{"class":303},[165,2479,2017],{"class":199},[165,2481,385],{"class":423},[165,2483,733],{"class":307},[165,2485,2486,2488,2490,2493],{"class":167,"line":681},[165,2487,2417],{"class":423},[165,2489,856],{"class":307},[165,2491,2492],{"class":2031}," 403",[165,2494,742],{"class":307},[165,2496,2497,2499,2501,2503,2506,2508],{"class":167,"line":714},[165,2498,2429],{"class":423},[165,2500,856],{"class":307},[165,2502,332],{"class":307},[165,2504,2505],{"class":175},"Interdit",[165,2507,338],{"class":307},[165,2509,742],{"class":307},[165,2511,2512,2514],{"class":167,"line":719},[165,2513,2445],{"class":307},[165,2515,678],{"class":423},[165,2517,2518],{"class":167,"line":736},[165,2519,2452],{"class":307},[165,2521,2522],{"class":167,"line":745},[165,2523,1844],{"class":307},[165,2525,2526,2529,2531,2533,2535,2537,2539,2541],{"class":167,"line":753},[165,2527,2528],{"class":199},"  handler",[165,2530,856],{"class":307},[165,2532,2323],{"class":391},[165,2534,1227],{"class":307},[165,2536,1230],{"class":895},[165,2538,903],{"class":307},[165,2540,392],{"class":391},[165,2542,395],{"class":307},[165,2544,2545,2547,2549,2551,2553,2555,2557,2559,2561,2563,2565,2567,2569],{"class":167,"line":761},[165,2546,2338],{"class":391},[165,2548,688],{"class":311},[165,2550,407],{"class":307},[165,2552,1247],{"class":303},[165,2554,1250],{"class":199},[165,2556,385],{"class":423},[165,2558,1230],{"class":311},[165,2560,315],{"class":307},[165,2562,1176],{"class":311},[165,2564,35],{"class":307},[165,2566,1263],{"class":199},[165,2568,385],{"class":423},[165,2570,733],{"class":307},[165,2572,2573,2576,2578,2580,2582,2584,2586,2588,2590,2592],{"class":167,"line":766},[165,2574,2575],{"class":423},"      url",[165,2577,856],{"class":307},[165,2579,1176],{"class":311},[165,2581,35],{"class":307},[165,2583,1281],{"class":199},[165,2585,388],{"class":423},[165,2587,35],{"class":307},[165,2589,551],{"class":199},[165,2591,388],{"class":423},[165,2593,742],{"class":307},[165,2595,2596,2598,2600,2602,2604],{"class":167,"line":783},[165,2597,2055],{"class":307},[165,2599,903],{"class":423},[165,2601,35],{"class":307},[165,2603,1302],{"class":311},[165,2605,678],{"class":423},[165,2607,2608],{"class":167,"line":791},[165,2609,370],{"emptyLinePlaceholder":369},[165,2611,2612,2614,2616,2618,2620,2622,2624],{"class":167,"line":798},[165,2613,2338],{"class":391},[165,2615,1315],{"class":311},[165,2617,407],{"class":307},[165,2619,1320],{"class":199},[165,2621,385],{"class":423},[165,2623,1230],{"class":311},[165,2625,678],{"class":423},[165,2627,2628,2630,2632,2634,2636,2638,2640,2642,2644],{"class":167,"line":1572},[165,2629,2338],{"class":391},[165,2631,1333],{"class":311},[165,2633,407],{"class":307},[165,2635,1157],{"class":199},[165,2637,385],{"class":423},[165,2639,1040],{"class":311},[165,2641,35],{"class":307},[165,2643,551],{"class":311},[165,2645,678],{"class":423},[165,2647,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678],{"class":167,"line":1577},[165,2649,2338],{"class":391},[165,2651,1354],{"class":311},[165,2653,407],{"class":307},[165,2655,1359],{"class":307},[165,2657,1362],{"class":199},[165,2659,385],{"class":423},[165,2661,938],{"class":307},[165,2663,1369],{"class":175},[165,2665,1372],{"class":307},[165,2667,1375],{"class":311},[165,2669,1378],{"class":307},[165,2671,315],{"class":307},[165,2673,1315],{"class":311},[165,2675,903],{"class":423},[165,2677,35],{"class":307},[165,2679,1389],{"class":311},[165,2681,2682],{"class":167,"line":1582},[165,2683,370],{"emptyLinePlaceholder":369},[165,2685,2686,2689,2691,2693,2695,2697,2699,2701,2703,2705,2707,2709,2711,2713,2715,2717],{"class":167,"line":1603},[165,2687,2688],{"class":303},"    await",[165,2690,1401],{"class":199},[165,2692,385],{"class":423},[165,2694,338],{"class":307},[165,2696,1408],{"class":175},[165,2698,338],{"class":307},[165,2700,903],{"class":423},[165,2702,35],{"class":307},[165,2704,1417],{"class":199},[165,2706,385],{"class":423},[165,2708,1375],{"class":311},[165,2710,315],{"class":307},[165,2712,688],{"class":311},[165,2714,35],{"class":307},[165,2716,551],{"class":311},[165,2718,678],{"class":423},[165,2720,2721],{"class":167,"line":1637},[165,2722,370],{"emptyLinePlaceholder":369},[165,2724,2725,2727,2729,2731,2733,2735],{"class":167,"line":1642},[165,2726,2338],{"class":391},[165,2728,404],{"class":311},[165,2730,407],{"class":307},[165,2732,410],{"class":307},[165,2734,392],{"class":391},[165,2736,395],{"class":307},[165,2738,2739,2742],{"class":167,"line":1653},[165,2740,2741],{"class":303},"      return",[165,2743,424],{"class":423},[165,2745,2746,2748,2750],{"class":167,"line":1664},[165,2747,442],{"class":307},[165,2749,433],{"class":423},[165,2751,436],{"class":307},[165,2753,2754,2756,2758],{"class":167,"line":1671},[165,2755,453],{"class":307},[165,2757,445],{"class":171},[165,2759,436],{"class":307},[165,2761,2762,2765,2767,2769,2771,2773,2775],{"class":167,"line":1678},[165,2763,2764],{"class":307},"            \u003C",[165,2766,456],{"class":423},[165,2768,459],{"class":307},[165,2770,1482],{"class":311},[165,2772,465],{"class":307},[165,2774,456],{"class":423},[165,2776,436],{"class":307},[165,2778,2780,2783,2785],{"class":167,"line":2779},41,[165,2781,2782],{"class":307},"          \u003C\u002F",[165,2784,445],{"class":171},[165,2786,436],{"class":307},[165,2788,2790,2792,2794,2796,2798,2800,2802],{"class":167,"line":2789},42,[165,2791,453],{"class":307},[165,2793,143],{"class":423},[165,2795,459],{"class":307},[165,2797,1507],{"class":311},[165,2799,465],{"class":307},[165,2801,143],{"class":423},[165,2803,436],{"class":307},[165,2805,2807,2809],{"class":167,"line":2806},43,[165,2808,453],{"class":307},[165,2810,1520],{"class":423},[165,2812,2814,2817,2819,2821,2823],{"class":167,"line":2813},44,[165,2815,2816],{"class":391},"            type",[165,2818,511],{"class":307},[165,2820,514],{"class":307},[165,2822,1532],{"class":175},[165,2824,1535],{"class":307},[165,2826,2828,2831,2833,2835],{"class":167,"line":2827},45,[165,2829,2830],{"class":391},"            value",[165,2832,1543],{"class":307},[165,2834,1546],{"class":311},[165,2836,877],{"class":307},[165,2838,2840],{"class":167,"line":2839},46,[165,2841,2842],{"class":391},"            autofocus\n",[165,2844,2846],{"class":167,"line":2845},47,[165,2847,2848],{"class":307},"          \u002F>\n",[165,2850,2852,2854,2856],{"class":167,"line":2851},48,[165,2853,475],{"class":307},[165,2855,433],{"class":423},[165,2857,436],{"class":307},[165,2859,2861],{"class":167,"line":2860},49,[165,2862,2863],{"class":423},"      )\n",[165,2865,2867],{"class":167,"line":2866},50,[165,2868,2452],{"class":307},[165,2870,2872],{"class":167,"line":2871},51,[165,2873,370],{"emptyLinePlaceholder":369},[165,2875,2877,2879,2881,2883,2885,2887,2889,2891,2893],{"class":167,"line":2876},52,[165,2878,2338],{"class":391},[165,2880,660],{"class":311},[165,2882,407],{"class":307},[165,2884,323],{"class":199},[165,2886,385],{"class":423},[165,2888,669],{"class":307},[165,2890,672],{"class":171},[165,2892,675],{"class":307},[165,2894,678],{"class":423},[165,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926],{"class":167,"line":2897},53,[165,2899,2338],{"class":391},[165,2901,308],{"class":307},[165,2903,688],{"class":423},[165,2905,856],{"class":307},[165,2907,1614],{"class":311},[165,2909,315],{"class":307},[165,2911,693],{"class":311},[165,2913,326],{"class":307},[165,2915,407],{"class":307},[165,2917,318],{"class":311},[165,2919,35],{"class":307},[165,2921,704],{"class":199},[165,2923,385],{"class":423},[165,2925,709],{"class":311},[165,2927,678],{"class":423},[165,2929,2931],{"class":167,"line":2930},54,[165,2932,370],{"emptyLinePlaceholder":369},[165,2934,2936,2938,2940,2942],{"class":167,"line":2935},55,[165,2937,420],{"class":303},[165,2939,351],{"class":199},[165,2941,385],{"class":423},[165,2943,733],{"class":307},[165,2945,2947,2950,2952,2954],{"class":167,"line":2946},56,[165,2948,2949],{"class":423},"      body",[165,2951,856],{"class":307},[165,2953,1614],{"class":311},[165,2955,742],{"class":307},[165,2957,2959,2962],{"class":167,"line":2958},57,[165,2960,2961],{"class":311},"      head",[165,2963,742],{"class":307},[165,2965,2967,2969],{"class":167,"line":2966},58,[165,2968,2055],{"class":307},[165,2970,678],{"class":423},[165,2972,2974],{"class":167,"line":2973},59,[165,2975,1844],{"class":307},[165,2977,2979,2981],{"class":167,"line":2978},60,[165,2980,801],{"class":307},[165,2982,678],{"class":311},[10,2984,2985,2986,2989],{},"Nous mettons en place un gestionnaire avant la requête pour vérifier l'",[14,2987,2988],{},"en-tête d'origine",". S'il y a un désaccord, une erreur est générée ; sinon, la requête passe au gestionnaire principal.",[2128,2991,2992],{},[10,2993,2994,2996,2997,2999,3000,3005,3006,3008,3009,3012],{},[165,2995,2183],{},"\nPour des informations complètes sur la ",[14,2998,2208],{},", consultez la ",[19,3001,3004],{"href":3002,"rel":3003},"https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FCross-Site_Request_Forgery_Prevention_Cheat_Sheet.html",[23],"Fiche de Prévention contre les Attaques CSRF"," par OWASP. Valider l'",[14,3007,2988],{}," est une approche de défense basique pour ",[14,3010,3011],{},"CSRF",". Cependant, il est conseillé de mettre en œuvre un mécanisme de protection plus robuste, tel qu'un token.",[143,3014,3016],{"id":3015},"conclusion","Conclusion",[10,3018,3019,3020,3022,3023,3025,3026,3028,3029,35],{},"Développer avec ",[14,3021,26],{}," et ",[14,3024,34],{}," est simple et offre une expérience de développeur exceptionnelle grâce à la possibilité d'utiliser ",[14,3027,131],{}," et un stockage de développement, éliminant le besoin du ",[14,3030,101],{},[10,3032,3033,3034,3022,3036,3038],{},"Profitez de votre parcours de développement avec ",[14,3035,26],{},[14,3037,34],{}," ! 🚀",[2128,3040,3041],{},[10,3042,3043,3045,3046,35],{},[165,3044,2183],{},"\nLe raccourcisseur d'URL est inspiré par ",[19,3047,3050],{"href":3048,"rel":3049},"https:\u002F\u002Fgithub.com\u002Fyusukebe\u002Furl-shortener",[23],"le Raccourcisseur d'URL de Yusuke Wada",[3052,3053,3054],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":160,"searchDepth":205,"depth":205,"links":3056},[3057,3058],{"id":145,"depth":205,"text":146},{"id":244,"depth":205,"text":245,"children":3059},[3060,3061],{"id":270,"depth":366,"text":271},{"id":1076,"depth":366,"text":1077},"aa8454be-94ba-4d46-8be0-8cf703c0bfed","2024-03-19","Apprenez à créer un raccourcisseur d'URL en utilisant Nitro et à le déployer sur Cloudflare Pages, permettant un développement dans l'edge sans effort.","md",{},"\u002Ffr\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages","---\ncontentId: aa8454be-94ba-4d46-8be0-8cf703c0bfed\ntitle: Créer un raccourcisseur d'URL avec Nitro et Cloudflare Pages\ndescription: Apprenez à créer un raccourcisseur d'URL en utilisant Nitro et à le déployer sur Cloudflare Pages, permettant un développement dans l'edge sans effort.\ndate: 2024-03-19\n---\n\nDans cet article, nous allons développer un **raccourcisseur d'URL** en utilisant [**Nitro**](https:\u002F\u002Fnitro.unjs.io) et le déployer sur [**Cloudflare Pages**](https:\u002F\u002Fpages.cloudflare.com\u002F).\n\nLe code source est accessible sur \u003CGitHubLink repo=\"barbapapazes\u002Furl-shortener\" \u002F>.\n\n**Nitro** représente une nouvelle génération de **kit d'outils serveur**. Il nous permet de construire des serveurs web avec toutes les fonctionnalités nécessaires et de les déployer à notre convenance.\n\n**Cloudflare Pages** est une plateforme conçue pour construire et héberger des sites web sur le edge. Elle prend en charge des services comme [**KV**](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F) pour créer des applications complètes et à état.\n\nNotre projet est un **raccourcisseur d'URL** simple qui facilite la conversion d'une longue URL en une version raccourcie. Nous allons utiliser le **Cloudflare KV** pour stocker les URL et le **serveur Nitro** pour gérer les requêtes.\n\nNous allons utiliser :\n\n- [**unstorage**](https:\u002F\u002Funstorage.unjs.io) pour rationaliser le processus de développement en abstraire la couche **KV**, éliminant ainsi le besoin du **Cloudflare Wrangler CLI**.\n- [**ohash**](https:\u002F\u002Fohash.unjs.io) pour générer un **hash** à partir de l'URL afin de prévenir les collisions.\n- [**nanojsx**](https:\u002F\u002Fnanojsx.io\u002F) pour construire les **pages HTML** en utilisant **TSX**.\n- [**pico.css**](https:\u002F\u002Fpicocss.com) pour styliser l'application.\n\n## Initialisation du Projet\n\nTout d'abord, créez un nouveau **projet Nitro** :\n\n```bash\nnpx giget@latest nitro url-shortener\n```\n\nEnsuite, naviguez jusqu'au projet et installez les **dépendances** requises :\n\n```bash\ncd url-shortener\nnpm install\n```\n\nDémarrez le **serveur de développement** pour voir la page par défaut de Nitro :\n\n```bash\nnpm run dev\n```\n\nOuvrez votre navigateur et visitez [http:\u002F\u002Flocalhost:3000](http:\u002F\u002Flocalhost:3000) pour vérifier la fonctionnalité.\n\n## Construction du Raccourcisseur d'URL\n\nDans un premier temps, installez les paquets nécessaires :\n\n```bash\nnpm install ohash nano-jsx\n```\n\n### Générer une URL Raccourcie\n\nCréez une **route** nommée `index.get.tsx` dans le répertoire `server\u002Froutes`. Cela servira de **page d'accueil** de notre raccourcisseur d'URL où les utilisateurs peuvent générer une URL raccourcie à partir d'une longue.\n\n```tsx [server\u002Froutes\u002Findex.get.tsx]\nimport { h, Helmet, renderSSR } from 'nano-jsx' \u002F\u002F le `h` est crucial ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineLazyEventHandler(() => {\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Raccourcisseur d'URL avec Nitro\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Raccourcir une URL\u003C\u002Fh2>\n        \u003Cform action=\"\u002Fcreate\" method=\"POST\">\n          \u003Cinput type=\"url\" name=\"url\" placeholder=\"URL à raccourcir\" autocomplete=\"off\" \u002F>\n          \u003Cbutton type=\"submit\">Créer\u003C\u002Fbutton>\n        \u003C\u002Fform>\n      \u003C\u002Fdiv>\n    )\n  }\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body, head } = Helmet.SSR(app)\n\n  const page = withTemplate({\n    body,\n    head,\n  })\n\n  return defineEventHandler(() => {\n    return page\n  })\n})\n```\n\nCette route présentera un formulaire permettant aux utilisateurs de saisir une URL à raccourcir. Lors de la soumission du formulaire, une **requête POST** est envoyée à la route `\u002Fcreate`.\n\nUn **gestionnaire d'événements paresseux** est utilisé pour générer la vue une seule fois, lorsque la requête atteint le serveur. La réponse est ensuite **mise en cache en mémoire** et réutilisée pour les requêtes futures, réduisant ainsi la charge computationnelle.\n\nLa fonction `withTemplate` sert d'utilitaire que nous devons construire.\n\n```ts [server\u002Fresources\u002Ftemplate.tsx]\ninterface LayoutProps {\n  body: string\n  head: string[]\n}\n\nexport function withTemplate(props: LayoutProps) {\n  const { head, body } = props\n\n  return \u002F* html *\u002F`\u003Chtml>\n      \u003Chead>\n       \u003Clink\n          rel=\"stylesheet\"\n          href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@picocss\u002Fpico@2\u002Fcss\u002Fpico.min.css\"\n        \u002F>\n        ${head.join('\\n')}\n      \u003C\u002Fhead>\n      \u003Cbody>\n        \u003Cheader class=\"container\">\n          \u003Ch1>\n            \u003Ca href=\"\u002F\">Raccourcisseur d'URL avec Nitro\u003C\u002Fa>\n          \u003C\u002Fh1>\n        \u003C\u002Fheader>\n        \u003Cmain class=\"container\">\n          ${body}\n        \u003C\u002Fmain>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>`\n}\n```\n\nCeci est un simple **template de chaîne** où nous incorporons le contenu de l'en-tête et du corps générés par **nano-jsx**.\n\n### Stockage de l'URL\n\nAvant de procéder, installez `zod` pour valider le corps de la requête.\n\n```bash\nnpm install zod\n```\n\nCréez la route `\u002Fcreate` pour gérer la **requête POST** et stocker l'URL dans le **KV**. Cette route est étiquetée `create.post.tsx` et se trouve dans le répertoire `server\u002Froutes`.\n\n```tsx [server\u002Froutes\u002Fcreate.post.tsx]\nimport { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod' \u002F\u002F le `h` est essentiel ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, z.object({\n    url: z.string().url(),\n  }).parse)\n\n  const requestURL = getRequestURL(event)\n  const id = hash(body.url)\n  const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n  await useStorage('data').setItem(id, body.url)\n\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Créé\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Créé et prêt\u003C\u002Fh2>\n        \u003Cinput\n          type=\"text\"\n          value={shortenURL}\n          autofocus\n        \u002F>\n      \u003C\u002Fdiv>\n    )\n  }\n\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body: nanoBody, head } = Helmet.SSR(app)\n\n  return withTemplate({\n    body: nanoBody,\n    head,\n  })\n})\n```\n\nDans ce segment, nous utilisons la fonction `readValidatedBody` pour **valider le corps de la requête**. Cela assure que le champ `url` est une URL légitime, sinon une erreur est générée.\n\nNous récupérons l'**URL de la requête** en utilisant la fonction `getRequestURL` de `h3`.\n\nLe **hash** est créé à partir de l'URL du corps en utilisant la fonction `hash` de **ohash**, garantissant une génération de hash cohérente pour éviter les collisions.\n\nL'URL est stockée dans le **KV** en utilisant la fonction `useStorage` de **unstorage**, employant le namespace `data` pour stocker les URL, préconfiguré pour notre utilisation.\n\n\u003CCalloutReadMore text=\"Stockage KV\" href=\"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fstorage#usage\" \u002F>\n\nÀ la fin de ce gestionnaire d'événements, une route est renvoyée avec l'URL raccourcie que les utilisateurs peuvent copier et utiliser.\n\nPour le développement, tout fonctionne correctement, mais nous devons mettre à jour cette configuration, `nitro.config.ts`, pour l'environnement de production.\n\n```ts [nitro.config.ts]\nexport default defineNitroConfig({\n  srcDir: 'server',\n  $production: {\n    storage: { data: { driver: 'cloudflare-kv-binding', binding: 'url-shortener' } },\n  },\n})\n```\n\nDans cette configuration, nous définissons le namespace `data`, comme dans le développement, afin d'utiliser le driver **cloudflare-kv-binding** et le binding `url-shortener`, spécifié sous la clé `$production`. Cela indique que la configuration s'applique uniquement à l'**environnement de production** pour accéder au **Cloudflare KV**.\n\n\u003CCalloutReadMore text=\"Configuration spécifique à l'environnement\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Fc12#environment-specific-configuration\" \u002F>\n\n### Redirection vers l'URL Originale\n\nEnfin, nous devons établir une route pour gérer les URL raccourcies et rediriger les utilisateurs vers l'URL principale. Cette route est nommée `[id].get.ts` et est située dans le répertoire `server\u002Froutes`.\n\n```ts [server\u002Froutes\u002F[id].get.ts]\nexport default defineEventHandler(async (event) => {\n  const id = getRouterParam(event, 'short')\n\n  const value = await useStorage\u003Cstring>('data').getItem(id)\n\n  if (!value) {\n    throw createError({\n      statusCode: 404,\n      statusMessage: 'Non trouvé',\n    })\n  }\n\n  return sendRedirect(event, value)\n})\n```\n\nIci, nous obtenons simplement l'`id` à partir des paramètres du routeur et utilisons `useStorage` pour récupérer l'**URL** du **KV**. Si l'URL n'existe pas, une **erreur 404** est générée. Sinon, l'utilisateur est redirigé vers l'URL originale.\n\n## Déployer le Raccourcisseur d'URL\n\nFélicitations ! Nous avons réussi à créer un raccourcisseur d'URL de base en utilisant Nitro. La prochaine étape consiste à **le déployer** sur Cloudflare Pages. Rassurez-vous, c'est simple grâce aux **fournisseurs de déploiement sans configuration**.\n\n\u003CCalloutReadMore text=\"Fournisseurs Zero Config\" href=\"https:\u002F\u002Fnitro.unjs.io\u002Fdeploy#zero-config-providers\" \u002F>\n\n> [!WARNING]\n> Un compte Cloudflare et un dépôt GitHub sont nécessaires pour continuer avec le déploiement.\nTout d'abord, connectez-vous au tableau de bord Cloudflare et sélectionnez un compte. Naviguez vers Accueil du compte, choisissez Workers & Pages, et créez une nouvelle application en utilisant le bouton en haut à droite. Sélectionnez l'onglet Pages et suivez les instructions pour établir une connexion GitHub.\n\nSélectionnez le dépôt, ajoutez la commande de construction `npm run build`, spécifiez le répertoire de sortie `dist`, incluez une variable d'environnement `NODE_ENV` avec la valeur `production`, puis enregistrez et déployez.\n\nMaintenant, attendons que le déploiement soit terminé. Une fois terminé, vous pouvez accéder au raccourcisseur d'URL en utilisant l'URL fournie par **Cloudflare Pages**.\n\n**Cependant**, il se peut qu'il ne fonctionne pas adéquatement initialement, car un **namespace KV** n'a pas été lié. Cela nécessite un lien manuel. Allez dans les paramètres du projet, sélectionnez les fonctions et faites défiler jusqu'à ce que vous trouviez **liaisons de namespace KV**. Ajoutez une liaison avec le nom `url-shortener` et choisissez le namespace désiré.\n\n> [!NOTE]\n> Créer un **namespace KV** est réalisable dans la section **Workers & Pages**.\nRedéployez votre application pour tenir compte de la nouvelle liaison de namespace KV. Une fois terminé, le raccourcisseur d'URL sera pleinement fonctionnel. :sparkles:\n\n> [!NOTE]\n> Si vous n'avez pas l'intention de maintenir le projet, n'oubliez pas de le supprimer du tableau de bord Cloudflare Pages afin d'éviter des dépenses inutiles.\n\n## Améliorations et Considérations Futures\n\nPour améliorer la sécurité, envisagez d'ajouter une simple **protection CSRF** en utilisant la syntaxe des objets gestionnaires. Modifiez le fichier `create.post.tsx` pour inclure un gestionnaire avant la requête pour la vérification de l'en-tête d'origine.\n\n```tsx [server\u002Froutes\u002Fcreate.post.tsx]\nimport { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod'\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler({\n  onBeforeResponse: async (event) => {\n    const requestURL = getRequestURL(event).origin\n    const origin = getRequestHeader(event, 'origin')\n\n    if (!origin) {\n      throw createError({\n        statusCode: 400,\n        statusMessage: 'Mauvaise requête',\n      })\n    }\n\n    if (origin !== requestURL) {\n      throw createError({\n        statusCode: 403,\n        statusMessage: 'Interdit',\n      })\n    }\n  },\n  handler: async (event) => {\n    const body = await readValidatedBody(event, z.object({\n      url: z.string().url(),\n    }).parse)\n\n    const requestURL = getRequestURL(event)\n    const id = hash(body.url)\n    const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n    await useStorage('data').setItem(id, body.url)\n\n    const App = () => {\n      return (\n        \u003Cdiv>\n          \u003CHelmet>\n            \u003Ctitle>Créé\u003C\u002Ftitle>\n          \u003C\u002FHelmet>\n          \u003Ch2>Créé et prêt\u003C\u002Fh2>\n          \u003Cinput\n            type=\"text\"\n            value={shortenURL}\n            autofocus\n          \u002F>\n        \u003C\u002Fdiv>\n      )\n    }\n\n    const app = renderSSR(\u003CApp \u002F>)\n    const { body: nanoBody, head } = Helmet.SSR(app)\n\n    return withTemplate({\n      body: nanoBody,\n      head,\n    })\n  },\n})\n```\n\nNous mettons en place un gestionnaire avant la requête pour vérifier l'**en-tête d'origine**. S'il y a un désaccord, une erreur est générée ; sinon, la requête passe au gestionnaire principal.\n\n> [!NOTE]\n> Pour des informations complètes sur la **protection CSRF**, consultez la [Fiche de Prévention contre les Attaques CSRF](https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FCross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) par OWASP. Valider l'**en-tête d'origine** est une approche de défense basique pour **CSRF**. Cependant, il est conseillé de mettre en œuvre un mécanisme de protection plus robuste, tel qu'un token.\n\n## Conclusion\n\nDévelopper avec **Nitro** et **Cloudflare Pages** est simple et offre une expérience de développeur exceptionnelle grâce à la possibilité d'utiliser **TSX** et un stockage de développement, éliminant le besoin du **Cloudflare Wrangler CLI**.\n\nProfitez de votre parcours de développement avec **Nitro** et **Cloudflare Pages** ! 🚀\n\n> [!NOTE]\n> Le raccourcisseur d'URL est inspiré par [le Raccourcisseur d'URL de Yusuke Wada](https:\u002F\u002Fgithub.com\u002Fyusukebe\u002Furl-shortener).\n",null,{"title":5,"description":3064},{"loc":3067},"fr\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages","C_B2fnQfNM7IHG-Sccv50NWq-LKu-LWbjenWAuB37RE",1790086904839]