[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-frontend-roadmap-you-have-to-pass":44,"search-suggestions":535,"path-membership-frontend-roadmap-you-have-to-pass":577,"related-frontend-roadmap-you-have-to-pass":578,"code:ts:true:1guz1uk":710,"code:ts:true:19h679w":711,"code:text:true:xy143w":712,"comments-01a13bf6-f5c5-73fa-b352-749b8f0f4068":713},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":76,"playground":48,"body":77,"bodyMd":526,"seo":527,"translationGroupId":529,"series":48,"podcastUrl":48,"verticalId":5,"thread":530,"assessments":532,"translations":533,"quiz":48},"01a13bf6-f5c5-73fa-b352-749b8f0f4068","frontend-roadmap-you-have-to-pass","I Turned 44 Blog Posts Into a Frontend Roadmap You Have to Pass",null,"A free frontend roadmap where every level is an article and a quiz you have to pass. Skip what you already know, no sign-up to start, cheat sheet inside.","\u002Fmedia\u002Fcovers\u002Ffrontend-roadmap-you-have-to-pass.png",18,"2026-10-14T19:49:01.265Z",12,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"javascript","JavaScript",[64,67,70,73],{"slug":65,"name":66,"color":48},"webdev","Webdev",{"slug":68,"name":69,"color":48},"frontend","Frontend",{"slug":71,"name":72,"color":48},"career","Career",{"slug":74,"name":75,"color":48},"showdev","Showdev",{"assessments":19},{"blocks":78,"version":525},[79,83,86,89,92,97,100,109,112,115,118,131,134,137,143,146,149,155,158,161,164,217,220,223,226,229,233,236,239,242,245,248,251,254,257,263,266,269,272,275,282,285,288,291,294,297,300,303,306,309,312,315,318,321,324,327,330,333,336,339,342,345,348,351,355,358,365,368,376,379,382,392,395,398,401,404,407,410,413,416,419,422,425,428,431,434,437,440,443,484,487,490,495,498,505,508,511,514,517,520],{"id":80,"html":81,"type":82},"b1","\u003Cp>It&#39;s Sunday night, the interview is on Thursday, and the frontend roadmap in your other tab has more topics than you have evenings. You&#39;re sure about half of them and fairly sure about the rest.\u003C\u002Fp>","paragraph",{"id":84,"html":85,"type":82},"b2","\u003Cp>The roadmap can&#39;t tell you which &quot;fairly sure&quot; is real. It only knows what you ticked.\u003C\u002Fp>",{"id":87,"html":88,"type":82},"b3","\u003Cp>So test the topics before you read them. Here&#39;s what that looks like for HTML: ten questions drawn at random, seven right to pass, and the whole stage counts as cleared.\u003C\u002Fp>",{"id":90,"html":91,"type":82},"b4","\u003Cp>No account and no reading, just ten questions. Here&#39;s why that beats a checkbox, and how it works.\u003C\u002Fp>",{"id":93,"html":94,"text":95,"type":96,"level":31},"b5","What you&#39;ll learn","What you'll learn","heading",{"id":98,"html":99,"type":82},"b6","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":101,"type":102,"items":103,"ordered":18},"b7","list",[104,105,106,107,108],"Explain why a frontend roadmap you tick yourself overstates what you know, using a 2006 study of re-reading versus testing","Compare seven well-known frontend roadmaps and courses on price, sign-up, checks, skipping and certificates","Use checkpoints to skip the stages you already know and find the gaps you don&#39;t","Compute learning progress from quiz attempts instead of storing it","Tell when this path is the wrong tool, and what to use instead",{"id":110,"html":111,"text":111,"type":96,"level":31},"b8","Who this is for",{"id":113,"html":114,"type":82},"b9","\u003Cp>You&#39;ve shipped frontend code for a year or more, in Vue or React, and you want to find out quickly what you actually know: for an interview, a promotion or your own peace of mind. If you build learning tools, read &quot;Under the hood&quot;. If you&#39;re starting from zero, read &quot;When not to use it&quot; first.\u003C\u002Fp>",{"id":116,"html":117,"text":117,"type":96,"level":31},"b10","Table of contents",{"id":119,"type":102,"items":120,"ordered":18},"b11",[121,122,123,124,125,126,127,128,129,130],"\u003Ca href=\"#why-a-frontend-roadmap-you-tick-yourself-stops-working\">Why a frontend roadmap you tick yourself stops working\u003C\u002Fa>","\u003Ca href=\"#what-people-do-today-seven-frontend-roadmaps-and-courses\">What people do today: seven frontend roadmaps and courses\u003C\u002Fa>","\u003Ca href=\"#where-todays-options-break\">Where today&#39;s options break\u003C\u002Fa>","\u003Ca href=\"#the-mental-model\">The mental model\u003C\u002Fa>","\u003Ca href=\"#enter-the-frontend-developer-path\">Enter the Frontend Developer path\u003C\u002Fa>","\u003Ca href=\"#how-the-frontend-roadmap-works-in-four-steps\">How the frontend roadmap works, in four steps\u003C\u002Fa>","\u003Ca href=\"#under-the-hood-progress-computed-from-quiz-attempts\">Under the hood: progress computed from quiz attempts\u003C\u002Fa>","\u003Ca href=\"#when-not-to-use-it\">When not to use it\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet-the-path-at-a-glance\">Cheat sheet: the path at a glance\u003C\u002Fa>",{"id":132,"html":133,"text":133,"type":96,"level":31},"b12","Why a frontend roadmap you tick yourself stops working",{"id":135,"html":136,"type":82},"b13","\u003Cp>Strip a self-marked roadmap down to its data, and this is all of it:\u003C\u002Fp>",{"id":138,"code":139,"type":140,"language":141,"highlight":142},"b14","type Status = \"done\" | \"learning\" | \"skipped\" | \"pending\";\n\nconst progress: Record\u003Cstring, Status> = {\n  \"semantic-html\": \"done\",  \u002F\u002F ticked two years ago\n  \"event-loop\": \"done\",     \u002F\u002F read the article, felt fine\n  promises: \"skipped\",      \u002F\u002F \"I use them every day\"\n  specificity: \"pending\",\n};","code","ts",[],{"id":144,"html":145,"type":82},"b15","\u003Cp>Every value in that object is an opinion, typed in by the person it describes. Nothing can make it wrong, and that is the bug.\u003C\u002Fp>",{"id":147,"html":148,"type":82},"b16","\u003Cp>It fails in three ways:\u003C\u002Fp>",{"id":150,"type":102,"items":151,"ordered":17},"b17",[152,153,154],"\u003Cstrong>&quot;done&quot; records reading, not knowing.\u003C\u002Fstrong> Reaching the end of an article and answering a question about it are different events. The tick records the first one.","\u003Cstrong>&quot;skipped&quot; is a guess, in both directions.\u003C\u002Fstrong> You skip HTML because you&#39;ve written it for years. Whether you know what \u003Ccode>inert\u003C\u002Fcode> does, or what \u003Ccode>showModal()\u003C\u002Fcode> gives you that \u003Ccode>show()\u003C\u002Fcode> doesn&#39;t, the tick can&#39;t say. Some of what you skip, you don&#39;t know; some of what you re-read, you did.","\u003Cstrong>Nobody else can read it.\u003C\u002Fstrong> A lead, a mentor or an interviewer can&#39;t tell your &quot;done&quot; from anyone else&#39;s.",{"id":156,"html":157,"type":82},"b18","\u003Cp>The fix isn&#39;t a better checkbox. It&#39;s taking the checkbox away from the person being measured.\u003C\u002Fp>",{"id":159,"html":160,"text":160,"type":96,"level":31},"b19","What people do today: seven frontend roadmaps and courses",{"id":162,"html":163,"type":82},"b20","\u003Cp>Here&#39;s how the best-known options handle it, checked on their own pages on 14 October 2026:\u003C\u002Fp>",{"id":165,"head":166,"rows":173,"type":216},"b21",[167,168,169,170,171,172],"","Free?","Account","How your learning is checked","Skip what you know","Certificate",[174,181,187,193,199,205,210],[175,176,177,178,179,180],"\u003Ca href=\"https:\u002F\u002Froadmap.sh\u002Ffrontend\">roadmap.sh\u003C\u002Fa> frontend roadmap","Yes; AI quizzes: 2 on the free plan, unlimited on Pro ($10\u002Fmonth)","To save progress","You mark topics done; AI quizzes and self-graded flashcards sit alongside","Mark a topic &quot;skipped&quot; yourself","None found",[182,183,177,184,185,186],"\u003Ca href=\"https:\u002F\u002Fwww.freecodecamp.org\u002Flearn\">freeCodeCamp\u003C\u002Fa> v9 certifications","Yes","Questions after every lesson, 20-question module quizzes, 5 projects, then a 50-question closed-book exam","No placement test found","Yes, free",[188,183,189,190,191,192],"\u003Ca href=\"https:\u002F\u002Fwww.theodinproject.com\u002F\">The Odin Project\u003C\u002Fa>","Optional, to check off lessons","Projects, compared with other students&#39; solutions","Cherry-pick lessons and projects","&quot;We don&#39;t provide a certificate&quot;",[194,183,195,196,197,198],"\u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fcurriculum\u002F\">MDN Curriculum\u003C\u002Fa>","No","Not graded: a syllabus with recommended resources, plus &quot;Test your skills&quot; self-checks in MDN&#39;s Learn modules","A self-paced reading list","None",[200,201,183,202,203,204],"\u003Ca href=\"https:\u002F\u002Fwww.codecademy.com\u002Flearn\u002Fpaths\u002Ffront-end-engineer-career-path\">Codecademy Front-End Engineer\u003C\u002Fa>","No: Pro, from $15.99\u002Fmonth billed annually","103 quizzes, 77 projects, certification exams","&quot;Skip ahead to the exam&quot;","Professional certification (Pro)",[206,207,183,208,180,209],"\u003Ca href=\"https:\u002F\u002Fscrimba.com\u002Ffrontend-path-c0j\">Scrimba Frontend Developer path\u003C\u002Fa>","No: subscription","Coding challenges inside the screencasts, solo projects, some quizzes","Certificate of completion",[211,212,213,214,215,180],"\u003Ca href=\"https:\u002F\u002Fwww.frontendmentor.io\u002Flearning-paths\">Frontend Mentor learning paths\u003C\u002Fa>","Mostly","To submit (GitHub login)","Feedback on what you build: the community, plus AI review (1 a month free, every solution on Pro)","Pick any challenge","table",{"id":218,"html":219,"type":82},"b22","\u003Cp>Three of these deserve more than a row.\u003C\u002Fp>",{"id":221,"html":222,"type":82},"b23","\u003Cp>\u003Cstrong>roadmap.sh is the best map of what to learn.\u003C\u002Fstrong> Its roadmaps are free, the project has 369K GitHub stars, and the site counts more than 3.2 million registered users. If you want to know what exists, look there first; nothing here argues against it.\u003C\u002Fp>",{"id":224,"html":225,"type":82},"b24","\u003Cp>\u003Cstrong>freeCodeCamp&#39;s v9 certifications are the most rigorous free credential in that table, and more rigorous than the quizzes in this article.\u003C\u002Fstrong> The \u003Ca href=\"https:\u002F\u002Fwww.freecodecamp.org\u002Fnews\u002Ffreecodecamps-new-responsive-web-design-certification-is-now-live\u002F\">new Responsive Web Design certification\u003C\u002Fa> ends each lesson with three comprehension questions and each module with a 20-question quiz (18 right to pass). Then it asks for five certification projects before a 50-question, closed-book exam: one attempt a week, reviewed by staff before the result is released.\u003C\u002Fp>",{"id":227,"html":228,"type":82},"b25","\u003Cp>\u003Cstrong>The Odin Project is right about proof.\u003C\u002Fstrong> It gives no certificate because, in its FAQ&#39;s words, &quot;Employers will be much more impressed with your amazing personal portfolio of projects.&quot; Projects are the strongest evidence anywhere in that table.\u003C\u002Fp>",{"id":230,"html":231,"text":232,"type":96,"level":31},"b26","Where today&#39;s options break","Where today's options break",{"id":234,"html":235,"type":82},"b27","\u003Cp>None of these is broken. The gap only shows when you want four things at once: free, no sign-up to start, a check on every step, and a skip you have to earn.\u003C\u002Fp>",{"id":237,"html":238,"type":82},"b28","\u003Cp>On roadmap.sh you mark a topic done. Its AI-generated quizzes sit beside the roadmap rather than gating it: two on the free plan (&quot;These don&#39;t reset&quot;), unlimited on Pro at $10 a month. freeCodeCamp checks properly, but it is built to take you from the start: its own figure is 300 hours per certification, and the exam waits for five projects. Right for learning, heavy for finding out what you already know.\u003C\u002Fp>",{"id":240,"html":241,"type":82},"b29","\u003Cp>Codecademy built exactly the skip I wanted (&quot;Don&#39;t waste time learning what you already know&quot;) and put it behind Pro, from $15.99 a month billed annually. The Odin Project and MDN Curriculum have nothing to pass, Scrimba is paid, and Frontend Mentor checks what you build rather than what you know.\u003C\u002Fp>",{"id":243,"html":244,"type":82},"b30","\u003Cp>No single piece of what I built is new; quizzes, skipping, forks and certificates all exist in that table. What I couldn&#39;t find among those seven was all four together.\u003C\u002Fp>",{"id":246,"html":247,"text":247,"type":96,"level":31},"b31","The mental model",{"id":249,"html":250,"type":82},"b32","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> reading is input, answering is evidence, and progress should be computed from the evidence, never typed in by the person being measured.\u003C\u002Fp>",{"id":252,"html":253,"type":82},"b33","\u003Cp>The evidence isn&#39;t only for other people. Roediger and Karpicke&#39;s 2006 study, \u003Ca href=\"https:\u002F\u002Fdoi.org\u002F10.1111\u002Fj.1467-9280.2006.01693.x\">\u003Cem>Test-Enhanced Learning\u003C\u002Fem>\u003C\u002Fa> (Psychological Science 17(3), 249–255), compared re-reading with testing. On a test five minutes later, re-reading came out ahead. Two days later, and again a week later, testing did, even though re-reading had left students more confident. No feedback was given in that study: nobody told the tested students what they had missed.\u003C\u002Fp>",{"id":255,"html":256,"type":82},"b34","\u003Cp>Three things follow:\u003C\u002Fp>",{"id":258,"type":102,"items":259,"ordered":18},"b35",[260,261,262],"\u003Cstrong>Skipping is evidence arriving early.\u003C\u002Fstrong> If you can pass the check without reading, the reading would have been re-reading, the strategy that lost at two days and at a week.","\u003Cstrong>A check can be open-book and still be a check.\u003C\u002Fstrong> Its job is to make you answer, and to show you exactly where you couldn&#39;t.","\u003Cstrong>Failing is the useful part.\u003C\u002Fstrong> A wrong answer that arrives with an explanation is a reading list. A tick never told you anything.",{"id":264,"html":265,"text":265,"type":96,"level":31},"b36","Enter the Frontend Developer path",{"id":267,"html":268,"type":82},"b37","\u003Cp>I&#39;d already written the articles. Between July and October, 44 of my posts each taught one piece of frontend, from \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdialog-element-native-modal\">the native \u003Ccode>&lt;dialog&gt;\u003C\u002Fcode>\u003C\u002Fa> to the back\u002Fforward cache, and every one has a quiz. What they lacked was an order, and a reason to take the quiz.\u003C\u002Fp>",{"id":270,"html":271,"type":82},"b38","\u003Cp>So I put them on a map: a \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer\">free frontend developer roadmap\u003C\u002Fa> on bestpractic.org, where every step is an article and a quiz you have to pass. Read it. Pass it. Unlock the next stage.\u003C\u002Fp>",{"id":273,"html":274,"type":82},"b39","\u003Cp>Honestly, in one sentence: \u003Cstrong>44 existing articles and their 336 quiz questions, in 7 stages that each close with a checkpoint, free, with no account needed to start, about 16 hours by the site&#39;s own estimate, and 7 more levels still being written.\u003C\u002Fstrong>\u003C\u002Fp>",{"id":276,"type":102,"items":277,"ordered":18},"b40",[278,279,280,281],"🗺️ The map: \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer\">https:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer\u003C\u002Fa>","⏭️ The HTML checkpoint: \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer\u002Fcheckpoint\u002Fhtml\">https:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer\u002Fcheckpoint\u002Fhtml\u003C\u002Fa>","1️⃣ Level 1, the native \u003Ccode>&lt;dialog&gt;\u003C\u002Fcode>: \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdialog-element-native-modal?path=frontend-developer\">https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdialog-element-native-modal?path=frontend-developer\u003C\u002Fa>","📚 All learning paths: \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fpaths\">https:\u002F\u002Fbestpractic.org\u002Fpaths\u003C\u002Fa>",{"id":283,"html":284,"text":284,"type":96,"level":31},"b41","How the frontend roadmap works, in four steps",{"id":286,"html":287,"text":287,"type":96,"level":43},"b42","Step 1: the map",{"id":289,"html":290,"type":82},"b43","\u003Cp>The route has seven stages: HTML, CSS, JavaScript, Vue or React, Nuxt or Next.js, TypeScript, and Ship it, the production stage. The two forks make nine stage cards, each pair sharing one tier of the map: Nuxt follows Vue, Next.js follows React, and TypeScript opens from either side.\u003C\u002Fp>",{"id":292,"html":293,"type":82},"b44","\u003Cp>The longest route is 34 levels, through Vue and Nuxt; through React and Next.js it is 33. JavaScript includes \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-javascript-event-loop\">the event loop\u003C\u002Fa>, Next.js starts with \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-cache-components-explained\">Cache Components\u003C\u002Fa>, and TypeScript with \u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-typescript-generics\">generics\u003C\u002Fa>. Seven more levels show as &quot;Being written&quot;: from-zero openers for HTML, JavaScript, Vue and React, then the box model and cascade, Flexbox, and inferred type predicates.\u003C\u002Fp>",{"id":295,"html":296,"type":82},"b45","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> the percentage measures your route, not the whole map. Each tier counts once, so finishing one branch is 100%.\u003C\u002Fp>",{"id":298,"html":299,"text":299,"type":96,"level":43},"b46","Step 2: a level is an article plus its quiz",{"id":301,"html":302,"type":82},"b47","\u003Cp>Each level is one of the 44 articles plus its quiz: 6 to 9 questions, four options each. The article&#39;s badge says where it sits (&quot;Level 1 of 4 in HTML&quot;). Pass the quiz at 70% to clear the level. Your best score counts, retakes are unlimited, and each cleared level leaves a crystal on the map, gold at 100%.\u003C\u002Fp>",{"id":304,"html":305,"type":82},"b48","\u003Cp>Answers stay hidden until you submit. Then every question is explained: why the right option is right and, if you picked another, why yours was wrong. Two-thirds also link to a source, mostly MDN and the frameworks&#39; own docs.\u003C\u002Fp>",{"id":307,"html":308,"type":82},"b49","\u003Cp>Levels open one at a time. A locked one is still readable: its drawer offers &quot;Read it anyway&quot;, because every article is free.\u003C\u002Fp>",{"id":310,"html":311,"type":82},"b50","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> the lock orders the route; it never hides the article. A quiz you passed before its level opened, from the blog, say, still counts.\u003C\u002Fp>",{"id":313,"html":314,"text":314,"type":96,"level":43},"b51","Step 3: the checkpoint, and how to skip a stage",{"id":316,"html":317,"type":82},"b52","\u003Cp>Every stage ends in a checkpoint: 10 questions drawn at random from that stage&#39;s level quizzes, a fresh draw on each attempt, 70% to pass. The last one, Ship it, draws 15. Beat it, and the stage is clear and the next one opens.\u003C\u002Fp>",{"id":319,"html":320,"type":82},"b53","\u003Cp>A checkpoint is open from the moment its stage is. That&#39;s the skip from the top of this article: &quot;Know HTML? Skip it&quot; goes straight there, and a pass clears HTML without a single level. The levels stay open, so you can go back for the crystals; skipping earns none.\u003C\u002Fp>",{"id":322,"html":323,"type":82},"b54","\u003Cp>Fail it, and the review explains every question; the next attempt is a new draw. One honest caveat: a checkpoint has no questions of its own, so if you have just cleared every level in a stage, you have already met every question it can ask.\u003C\u002Fp>",{"id":325,"html":326,"type":82},"b55","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> a checkpoint is a placement check built from the stage&#39;s own quizzes. It asks the same of someone who read everything and someone who read nothing.\u003C\u002Fp>",{"id":328,"html":329,"text":329,"type":96,"level":43},"b56","Step 4: your run, and the finish line",{"id":331,"html":332,"type":82},"b57","\u003Cp>No account is needed to start: a guest&#39;s run is saved on that device for a year, in a cookie set when you first start a quiz. Sign up, and your guest attempts move to your account, run and all.\u003C\u002Fp>",{"id":334,"html":335,"type":82},"b58","\u003Cp>The &quot;Your run&quot; card does the arithmetic. Clear level 1 and you&#39;re at 3%: one of five units in HTML (four levels plus the checkpoint), averaged over seven tiers. Beat the HTML checkpoint and you&#39;re at 14%.\u003C\u002Fp>",{"id":337,"html":338,"type":82},"b59","\u003Cp>You finish by beating one checkpoint per tier: seven passes, at least 75 questions. The certificate needs a free account and lives at a public link (\u003Ccode>\u002Fcertificates\u002FBP-…\u003C\u002Fcode>) that anyone can open to check it. It states what it certifies: earned by every checkpoint.\u003C\u002Fp>",{"id":340,"html":341,"type":82},"b60","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> finishing is defined by checkpoints alone, which is why a skipper and a completionist end up at the same finish line.\u003C\u002Fp>",{"id":343,"html":344,"text":344,"type":96,"level":31},"b61","Under the hood: progress computed from quiz attempts",{"id":346,"html":347,"type":82},"b62","\u003Cp>If you build learning tools, this is the part worth taking.\u003C\u002Fp>",{"id":349,"html":350,"type":82},"b63","\u003Cp>\u003Cstrong>Progress is computed, not kept.\u003C\u002Fstrong> The map never reads your progress from a table; every load works it out again from the map and your best attempt at each quiz. The real \u003Ccode>path-progress.ts\u003C\u002Fcode> is covered by 13 Vitest cases. This simplified sketch follows the same rules:\u003C\u002Fp>",{"id":352,"code":353,"type":140,"language":141,"highlight":354},"b64","\u002F\u002F A simplified sketch of path-progress.ts. The real one also places the \"You\"\n\u002F\u002F marker, follows the branch you started at a fork, and marks perfect scores.\ntype Stage = {\n  slug: string;\n  tier: number;              \u002F\u002F forks share a tier: Vue and React are both 4\n  unlockedBy: string[];      \u002F\u002F [] for HTML, [\"nuxt\", \"nextjs\"] for TypeScript\n  checkpoint: string;        \u002F\u002F the checkpoint's quiz id\n  levels: (string | null)[]; \u002F\u002F level quiz ids in order; null = \"Being written\"\n};\ntype Attempt = { quiz: string; score: number; maxScore: number };\n\nexport function pathProgress(stages: Stage[], attempts: Attempt[]) {\n  \u002F\u002F 1. Only your best attempt at each quiz counts, wherever you took it.\n  const best = new Map\u003Cstring, number>();\n  for (const { quiz, score, maxScore } of attempts) {\n    best.set(quiz, Math.max(best.get(quiz) ?? 0, (score \u002F maxScore) * 100));\n  }\n  const passed = (quiz: string) => (best.get(quiz) ?? 0) >= 70;\n\n  const ordered = [...stages].sort((a, b) => a.tier - b.tier);\n  const cleared = new Set\u003Cstring>();\n  const levels = new Map\u003Cstring, \"open\" | \"locked\" | \"cleared\">();\n\n  for (const stage of ordered) {\n    \u002F\u002F 2. A stage opens when ANY stage in its unlockedBy is cleared.\n    const open = stage.unlockedBy.length === 0 || stage.unlockedBy.some((s) => cleared.has(s));\n    \u002F\u002F 3. Its checkpoint, and nothing else, clears it.\n    if (open && passed(stage.checkpoint)) cleared.add(stage.slug);\n\n    \u002F\u002F 4. Levels open one at a time. A \"Being written\" level is skipped: it blocks nobody.\n    let previousPassed = true;\n    for (const quiz of stage.levels) {\n      if (quiz === null) continue;\n      const ok = passed(quiz);\n      levels.set(quiz, ok ? \"cleared\" : open && (previousPassed || cleared.has(stage.slug)) ? \"open\" : \"locked\");\n      previousPassed = ok;\n    }\n  }\n\n  \u002F\u002F 5. Percent along your route: each tier counts once, scored by its best\n  \u002F\u002F    stage, and a checkpoint is worth one level.\n  const tiers = [...new Set(ordered.map((s) => s.tier))];\n  const tierScore = (tier: number) =>\n    Math.max(...ordered.filter((s) => s.tier === tier).map((s) => {\n      if (cleared.has(s.slug)) return 1;\n      const written = s.levels.filter((q): q is string => q !== null);\n      return written.filter(passed).length \u002F (written.length + 1);\n    }));\n  const pct = Math.round((tiers.reduce((sum, t) => sum + tierScore(t), 0) \u002F tiers.length) * 100);\n\n  return { cleared, levels, pct };\n}",[],{"id":356,"html":357,"type":82},"b65","\u003Cp>That one decision pays for four features:\u003C\u002Fp>",{"id":359,"type":102,"items":360,"ordered":18},"b66",[361,362,363,364],"Guests get a working map with no account, because their quiz attempts were already rows, keyed to a cookie.","A quiz passed on the blog before the path existed counts the day the path does.","Adding a level needs no backfill.","Signing up keeps the run, because the run was only ever the attempts.",{"id":366,"html":367,"type":82},"b67","\u003Cp>A signed-in run also writes its latest percentage to an enrollment record, after the fact; the map never reads it back. The rest follows the same instinct:\u003C\u002Fp>",{"id":369,"type":102,"items":370,"ordered":18},"b68",[371,372,373,374,375],"\u003Cstrong>The map is the same for everyone; you are fetched.\u003C\u002Fstrong> The server renders the map without anyone&#39;s progress in it, so every visitor gets the same one. Your place on it arrives after hydration, and the default is a newcomer&#39;s view, so a first visit never shows padlocks flicking open.","\u003Cstrong>Looking costs nothing.\u003C\u002Fstrong> The progress endpoint never sets a cookie. The anonymous ID is only set when there is something to save, like a quiz attempt, so a crawler that only looks never gets one.","\u003Cstrong>Forks are data.\u003C\u002Fstrong> Each stage lists the stages that open it (\u003Ccode>unlockedBy\u003C\u002Fcode>), and the map draws one road per entry: the line, the fork and the join, from one rule.","\u003Cstrong>Checkpoints are assembled, not written.\u003C\u002Fstrong> Each release builds a stage&#39;s checkpoint from its level quizzes. They issue no certificate of their own; the path certificate is the proof.","\u003Cstrong>The catalogue is code.\u003C\u002Fstrong> A level whose article isn&#39;t written yet shows as &quot;Being written&quot; and blocks nobody. Publishing that article and its quiz under the planned slug is all the map needs; the next release picks it up.",{"id":377,"html":378,"text":378,"type":96,"level":31},"b69","When not to use it",{"id":380,"html":381,"type":82},"b70","\u003Cp>Here&#39;s when this path is the wrong tool, and what to use instead.\u003C\u002Fp>",{"id":383,"type":102,"items":384,"ordered":18},"b71",[385,386,387,388,389,390,391],"\u003Cstrong>You&#39;re starting from zero today.\u003C\u002Fstrong> The from-zero openers aren&#39;t written yet, and the first live level, the native \u003Ccode>&lt;dialog&gt;\u003C\u002Fcode>, assumes you&#39;ve built a modal before. Start with the \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fcurriculum\u002F\">MDN Curriculum\u003C\u002Fa>, free and beginner-level, or \u003Ca href=\"https:\u002F\u002Fwww.freecodecamp.org\u002Flearn\">freeCodeCamp\u003C\u002Fa> for questions after every lesson, and come back for the checkpoints.","\u003Cstrong>You learn by building.\u003C\u002Fstrong> A quiz checks understanding, not whether you can ship a page. \u003Ca href=\"https:\u002F\u002Fwww.theodinproject.com\u002F\">The Odin Project\u003C\u002Fa> is free and project-based, and \u003Ca href=\"https:\u002F\u002Fwww.frontendmentor.io\u002Flearning-paths\">Frontend Mentor\u003C\u002Fa> gives you real designs to build, with feedback on the result.","\u003Cstrong>You need a closed-book credential.\u003C\u002Fstrong> These quizzes are open-book and unsupervised. freeCodeCamp&#39;s v9 certifications end in a closed-book exam and cost nothing.","\u003Cstrong>You learn from video.\u003C\u002Fstrong> Every level here is text. \u003Ca href=\"https:\u002F\u002Fscrimba.com\u002Ffrontend-path-c0j\">Scrimba&#39;s Frontend Developer path\u003C\u002Fa> is about 81 hours of interactive screencasts, on a paid plan.","\u003Cstrong>You want the whole map of the field.\u003C\u002Fstrong> This is one path through 44 articles; \u003Ca href=\"https:\u002F\u002Froadmap.sh\u002Ffrontend\">roadmap.sh\u003C\u002Fa> shows everything around it.","\u003Cstrong>You work in Angular or Svelte.\u003C\u002Fstrong> The forks are Vue and React only.","\u003Cstrong>You&#39;ll switch devices before signing up.\u003C\u002Fstrong> A guest run lives in one browser, and clearing its cookies loses it. Create the free account first.",{"id":393,"html":394,"text":394,"type":96,"level":31},"b72","FAQ",{"id":396,"html":397,"text":397,"type":96,"level":43},"b73","Is this frontend roadmap really free?",{"id":399,"html":400,"type":82},"b74","\u003Cp>Yes. Every article, quiz and checkpoint on the path is free, and so is the account the certificate needs.\u003C\u002Fp>",{"id":402,"html":403,"text":403,"type":96,"level":43},"b75","Do I need an account to start the frontend roadmap?",{"id":405,"html":406,"type":82},"b76","\u003Cp>No. A guest run is saved on that device for a year. A free account carries it to every device, keeps what you did as a guest, and is required for the certificate.\u003C\u002Fp>",{"id":408,"html":409,"text":409,"type":96,"level":43},"b77","How long does the frontend developer roadmap take?",{"id":411,"html":412,"type":82},"b78","\u003Cp>The site estimates about 16 hours for one route; the read-time labels alone add up to 324 minutes on the Vue route and 302 on the React one. Skip everything you can, and the floor is seven checkpoints: 75 questions.\u003C\u002Fp>",{"id":414,"html":415,"text":415,"type":96,"level":43},"b79","Should I learn Vue or React first?",{"id":417,"html":418,"type":82},"b80","\u003Cp>The path doesn&#39;t rank them. Both open when you clear JavaScript, and TypeScript opens after either Nuxt or Next.js, so neither branch is a dead end. Pick the one your job, or the job you want, uses; your percentage only counts one branch.\u003C\u002Fp>",{"id":420,"html":421,"text":421,"type":96,"level":43},"b81","Can I skip HTML and CSS?",{"id":423,"html":424,"type":82},"b82","\u003Cp>Yes, if you can pass their checkpoints, 7 of 10 on each. Beat HTML&#39;s and CSS opens; beat CSS&#39;s and JavaScript opens. A stage only counts once it is open, so you skip in order.\u003C\u002Fp>",{"id":426,"html":427,"text":427,"type":96,"level":43},"b83","What happens if I fail a checkpoint?",{"id":429,"html":430,"type":82},"b84","\u003Cp>Nothing is lost. The review explains every question you missed, and you can retake it straight away: each attempt is a new random draw, with no cooldown and no attempt limit, and your best result counts.\u003C\u002Fp>",{"id":432,"html":433,"text":433,"type":96,"level":43},"b85","Is the frontend developer certificate worth anything?",{"id":435,"html":436,"type":82},"b86","\u003Cp>It records one thing: that the named account passed one checkpoint per stage at 70% or better, open-book, at least 75 questions in all. Anyone can open its public link to check it. It is not a closed-book credential; freeCodeCamp&#39;s is the free one built for that. Treat it as a receipt, not a diploma.\u003C\u002Fp>",{"id":438,"html":439,"text":439,"type":96,"level":31},"b87","Cheat sheet: the path at a glance",{"id":441,"html":442,"type":82},"b88","\u003Cp>The numbers below come from the live map as of 14 October 2026; &quot;live levels&quot; counts written levels only.\u003C\u002Fp>",{"id":444,"head":445,"rows":450,"type":216},"b89",[446,447,448,449],"Stage","Live levels","Checkpoint","Two example levels",[451,456,460,464,467,470,473,476,480],[452,453,454,455],"1 · HTML","4","10 questions, 7 to pass","Native \u003Ccode>&lt;dialog&gt;\u003C\u002Fcode>, The inert attribute",[457,458,454,459],"2 · CSS","5","Specificity with \u003Ccode>:where()\u003C\u002Fcode>, Viewport units: dvh, svh, lvh",[461,462,454,463],"3 · JavaScript","6","Closures, The event loop",[465,462,454,466],"4 · Vue (or React)","ref vs reactive, Composables &amp; shared state",[468,458,454,469],"4 · React (or Vue)","Re-render vs remount, React Compiler",[471,458,454,472],"5 · Nuxt (after Vue)","useAsyncData keys, Hydration mismatches",[474,458,454,475],"5 · Next.js (after React)","Cache Components, proxy.ts",[477,478,454,479],"6 · TypeScript","3","Generics, \u003Ccode>satisfies\u003C\u002Fcode>",[481,458,482,483],"7 · Ship it","15 questions, 11 to pass","Core Web Vitals, Web Workers",{"id":485,"html":486,"type":82},"b90","\u003Cp>That&#39;s 44 written levels across both forks, 34 on the Vue route and 33 on the React route, plus 7 being written.\u003C\u002Fp>",{"id":488,"html":489,"type":82},"b91","\u003Cp>The rules in five lines:\u003C\u002Fp>",{"id":491,"code":492,"type":140,"language":493,"highlight":494},"b92","level       article + quiz (6–9 questions). Pass at 70%; your best attempt counts.\norder       Levels open one at a time. A locked article is still free to read.\ncheckpoint  10 random questions from the stage (15 for Ship it). 70% clears it.\nskip        A checkpoint is open as soon as its stage is. Beat it early to skip.\nfinish      One checkpoint per tier. Certificate: free account, public link.","text",[],{"id":496,"html":497,"text":497,"type":96,"level":31},"b93","Key takeaways",{"id":499,"type":102,"items":500,"ordered":18},"b94",[501,502,503,504],"A tick you give yourself records what you read, not what you can answer. Compute progress from answers.","In Roediger and Karpicke&#39;s 2006 study, re-reading won at five minutes; testing won at two days and at a week, with no feedback.","Skipping is evidence arriving early: pass the check, and the reading was optional.","Building a learning tool? Store the attempts and derive everything else.",{"id":506,"html":507,"type":82},"b95","\u003Cp>Back to Sunday night, and the roadmap that only knows what you ticked. Spend the first evening on checkpoints instead of chapters. The stages you pass are done, and the ones you fail hand you every missed question, explained: a better reading list than a column of ticks ever was.\u003C\u002Fp>",{"id":509,"html":510,"type":82},"b96","\u003Cp>Which checkpoint do you think you could pass without reading anything, and which one would surprise you?\u003C\u002Fp>",{"id":512,"type":513},"b97","divider",{"id":515,"html":516,"type":82},"b98","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":518,"html":519,"type":82},"b99","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":521,"type":102,"items":522,"ordered":18},"b100",[523,524],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>",1,"It's Sunday night, the interview is on Thursday, and the frontend roadmap in your other tab has more topics than you have evenings. You're sure about half of them and fairly sure about the rest.\n\nThe roadmap can't tell you which \"fairly sure\" is real. It only knows what you ticked.\n\nSo test the topics before you read them. Here's what that looks like for HTML: ten questions drawn at random, seven right to pass, and the whole stage counts as cleared.\n\nNo account and no reading, just ten questions. Here's why that beats a checkbox, and how it works.\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Explain why a frontend roadmap you tick yourself overstates what you know, using a 2006 study of re-reading versus testing\n- Compare seven well-known frontend roadmaps and courses on price, sign-up, checks, skipping and certificates\n- Use checkpoints to skip the stages you already know and find the gaps you don't\n- Compute learning progress from quiz attempts instead of storing it\n- Tell when this path is the wrong tool, and what to use instead\n\n## Who this is for\n\nYou've shipped frontend code for a year or more, in Vue or React, and you want to find out quickly what you actually know: for an interview, a promotion or your own peace of mind. If you build learning tools, read \"Under the hood\". If you're starting from zero, read \"When not to use it\" first.\n\n## Table of contents\n\n- [Why a frontend roadmap you tick yourself stops working](#why-a-frontend-roadmap-you-tick-yourself-stops-working)\n- [What people do today: seven frontend roadmaps and courses](#what-people-do-today-seven-frontend-roadmaps-and-courses)\n- [Where today's options break](#where-todays-options-break)\n- [The mental model](#the-mental-model)\n- [Enter the Frontend Developer path](#enter-the-frontend-developer-path)\n- [How the frontend roadmap works, in four steps](#how-the-frontend-roadmap-works-in-four-steps)\n- [Under the hood: progress computed from quiz attempts](#under-the-hood-progress-computed-from-quiz-attempts)\n- [When not to use it](#when-not-to-use-it)\n- [FAQ](#faq)\n- [Cheat sheet: the path at a glance](#cheat-sheet-the-path-at-a-glance)\n\n## Why a frontend roadmap you tick yourself stops working\n\nStrip a self-marked roadmap down to its data, and this is all of it:\n\n```ts\ntype Status = \"done\" | \"learning\" | \"skipped\" | \"pending\";\n\nconst progress: Record\u003Cstring, Status> = {\n  \"semantic-html\": \"done\",  \u002F\u002F ticked two years ago\n  \"event-loop\": \"done\",     \u002F\u002F read the article, felt fine\n  promises: \"skipped\",      \u002F\u002F \"I use them every day\"\n  specificity: \"pending\",\n};\n```\n\nEvery value in that object is an opinion, typed in by the person it describes. Nothing can make it wrong, and that is the bug.\n\nIt fails in three ways:\n\n1. **\"done\" records reading, not knowing.** Reaching the end of an article and answering a question about it are different events. The tick records the first one.\n2. **\"skipped\" is a guess, in both directions.** You skip HTML because you've written it for years. Whether you know what `inert` does, or what `showModal()` gives you that `show()` doesn't, the tick can't say. Some of what you skip, you don't know; some of what you re-read, you did.\n3. **Nobody else can read it.** A lead, a mentor or an interviewer can't tell your \"done\" from anyone else's.\n\nThe fix isn't a better checkbox. It's taking the checkbox away from the person being measured.\n\n## What people do today: seven frontend roadmaps and courses\n\nHere's how the best-known options handle it, checked on their own pages on 14 October 2026:\n\n| | Free? | Account | How your learning is checked | Skip what you know | Certificate |\n|---|---|---|---|---|---|\n| [roadmap.sh](https:\u002F\u002Froadmap.sh\u002Ffrontend) frontend roadmap | Yes; AI quizzes: 2 on the free plan, unlimited on Pro ($10\u002Fmonth) | To save progress | You mark topics done; AI quizzes and self-graded flashcards sit alongside | Mark a topic \"skipped\" yourself | None found |\n| [freeCodeCamp](https:\u002F\u002Fwww.freecodecamp.org\u002Flearn) v9 certifications | Yes | To save progress | Questions after every lesson, 20-question module quizzes, 5 projects, then a 50-question closed-book exam | No placement test found | Yes, free |\n| [The Odin Project](https:\u002F\u002Fwww.theodinproject.com\u002F) | Yes | Optional, to check off lessons | Projects, compared with other students' solutions | Cherry-pick lessons and projects | \"We don't provide a certificate\" |\n| [MDN Curriculum](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fcurriculum\u002F) | Yes | No | Not graded: a syllabus with recommended resources, plus \"Test your skills\" self-checks in MDN's Learn modules | A self-paced reading list | None |\n| [Codecademy Front-End Engineer](https:\u002F\u002Fwww.codecademy.com\u002Flearn\u002Fpaths\u002Ffront-end-engineer-career-path) | No: Pro, from $15.99\u002Fmonth billed annually | Yes | 103 quizzes, 77 projects, certification exams | \"Skip ahead to the exam\" | Professional certification (Pro) |\n| [Scrimba Frontend Developer path](https:\u002F\u002Fscrimba.com\u002Ffrontend-path-c0j) | No: subscription | Yes | Coding challenges inside the screencasts, solo projects, some quizzes | None found | Certificate of completion |\n| [Frontend Mentor learning paths](https:\u002F\u002Fwww.frontendmentor.io\u002Flearning-paths) | Mostly | To submit (GitHub login) | Feedback on what you build: the community, plus AI review (1 a month free, every solution on Pro) | Pick any challenge | None found |\n\nThree of these deserve more than a row.\n\n**roadmap.sh is the best map of what to learn.** Its roadmaps are free, the project has 369K GitHub stars, and the site counts more than 3.2 million registered users. If you want to know what exists, look there first; nothing here argues against it.\n\n**freeCodeCamp's v9 certifications are the most rigorous free credential in that table, and more rigorous than the quizzes in this article.** The [new Responsive Web Design certification](https:\u002F\u002Fwww.freecodecamp.org\u002Fnews\u002Ffreecodecamps-new-responsive-web-design-certification-is-now-live\u002F) ends each lesson with three comprehension questions and each module with a 20-question quiz (18 right to pass). Then it asks for five certification projects before a 50-question, closed-book exam: one attempt a week, reviewed by staff before the result is released.\n\n**The Odin Project is right about proof.** It gives no certificate because, in its FAQ's words, \"Employers will be much more impressed with your amazing personal portfolio of projects.\" Projects are the strongest evidence anywhere in that table.\n\n## Where today's options break\n\nNone of these is broken. The gap only shows when you want four things at once: free, no sign-up to start, a check on every step, and a skip you have to earn.\n\nOn roadmap.sh you mark a topic done. Its AI-generated quizzes sit beside the roadmap rather than gating it: two on the free plan (\"These don't reset\"), unlimited on Pro at $10 a month. freeCodeCamp checks properly, but it is built to take you from the start: its own figure is 300 hours per certification, and the exam waits for five projects. Right for learning, heavy for finding out what you already know.\n\nCodecademy built exactly the skip I wanted (\"Don't waste time learning what you already know\") and put it behind Pro, from $15.99 a month billed annually. The Odin Project and MDN Curriculum have nothing to pass, Scrimba is paid, and Frontend Mentor checks what you build rather than what you know.\n\nNo single piece of what I built is new; quizzes, skipping, forks and certificates all exist in that table. What I couldn't find among those seven was all four together.\n\n## The mental model\n\n**The mental model:** reading is input, answering is evidence, and progress should be computed from the evidence, never typed in by the person being measured.\n\nThe evidence isn't only for other people. Roediger and Karpicke's 2006 study, [*Test-Enhanced Learning*](https:\u002F\u002Fdoi.org\u002F10.1111\u002Fj.1467-9280.2006.01693.x) (Psychological Science 17(3), 249–255), compared re-reading with testing. On a test five minutes later, re-reading came out ahead. Two days later, and again a week later, testing did, even though re-reading had left students more confident. No feedback was given in that study: nobody told the tested students what they had missed.\n\nThree things follow:\n\n- **Skipping is evidence arriving early.** If you can pass the check without reading, the reading would have been re-reading, the strategy that lost at two days and at a week.\n- **A check can be open-book and still be a check.** Its job is to make you answer, and to show you exactly where you couldn't.\n- **Failing is the useful part.** A wrong answer that arrives with an explanation is a reading list. A tick never told you anything.\n\n## Enter the Frontend Developer path\n\nI'd already written the articles. Between July and October, 44 of my posts each taught one piece of frontend, from [the native `\u003Cdialog>`](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdialog-element-native-modal) to the back\u002Fforward cache, and every one has a quiz. What they lacked was an order, and a reason to take the quiz.\n\nSo I put them on a map: a [free frontend developer roadmap](https:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer) on bestpractic.org, where every step is an article and a quiz you have to pass. Read it. Pass it. Unlock the next stage.\n\nHonestly, in one sentence: **44 existing articles and their 336 quiz questions, in 7 stages that each close with a checkpoint, free, with no account needed to start, about 16 hours by the site's own estimate, and 7 more levels still being written.**\n\n- 🗺️ The map: \u003Chttps:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer>\n- ⏭️ The HTML checkpoint: \u003Chttps:\u002F\u002Fbestpractic.org\u002Fpaths\u002Ffrontend-developer\u002Fcheckpoint\u002Fhtml>\n- 1️⃣ Level 1, the native `\u003Cdialog>`: \u003Chttps:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdialog-element-native-modal?path=frontend-developer>\n- 📚 All learning paths: \u003Chttps:\u002F\u002Fbestpractic.org\u002Fpaths>\n\n## How the frontend roadmap works, in four steps\n\n### Step 1: the map\n\nThe route has seven stages: HTML, CSS, JavaScript, Vue or React, Nuxt or Next.js, TypeScript, and Ship it, the production stage. The two forks make nine stage cards, each pair sharing one tier of the map: Nuxt follows Vue, Next.js follows React, and TypeScript opens from either side.\n\nThe longest route is 34 levels, through Vue and Nuxt; through React and Next.js it is 33. JavaScript includes [the event loop](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-javascript-event-loop), Next.js starts with [Cache Components](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnextjs-weekly-cache-components-explained), and TypeScript with [generics](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-typescript-generics). Seven more levels show as \"Being written\": from-zero openers for HTML, JavaScript, Vue and React, then the box model and cascade, Flexbox, and inferred type predicates.\n\n**Key concept:** the percentage measures your route, not the whole map. Each tier counts once, so finishing one branch is 100%.\n\n### Step 2: a level is an article plus its quiz\n\nEach level is one of the 44 articles plus its quiz: 6 to 9 questions, four options each. The article's badge says where it sits (\"Level 1 of 4 in HTML\"). Pass the quiz at 70% to clear the level. Your best score counts, retakes are unlimited, and each cleared level leaves a crystal on the map, gold at 100%.\n\nAnswers stay hidden until you submit. Then every question is explained: why the right option is right and, if you picked another, why yours was wrong. Two-thirds also link to a source, mostly MDN and the frameworks' own docs.\n\nLevels open one at a time. A locked one is still readable: its drawer offers \"Read it anyway\", because every article is free.\n\n**Key concept:** the lock orders the route; it never hides the article. A quiz you passed before its level opened, from the blog, say, still counts.\n\n### Step 3: the checkpoint, and how to skip a stage\n\nEvery stage ends in a checkpoint: 10 questions drawn at random from that stage's level quizzes, a fresh draw on each attempt, 70% to pass. The last one, Ship it, draws 15. Beat it, and the stage is clear and the next one opens.\n\nA checkpoint is open from the moment its stage is. That's the skip from the top of this article: \"Know HTML? Skip it\" goes straight there, and a pass clears HTML without a single level. The levels stay open, so you can go back for the crystals; skipping earns none.\n\nFail it, and the review explains every question; the next attempt is a new draw. One honest caveat: a checkpoint has no questions of its own, so if you have just cleared every level in a stage, you have already met every question it can ask.\n\n**Key concept:** a checkpoint is a placement check built from the stage's own quizzes. It asks the same of someone who read everything and someone who read nothing.\n\n### Step 4: your run, and the finish line\n\nNo account is needed to start: a guest's run is saved on that device for a year, in a cookie set when you first start a quiz. Sign up, and your guest attempts move to your account, run and all.\n\nThe \"Your run\" card does the arithmetic. Clear level 1 and you're at 3%: one of five units in HTML (four levels plus the checkpoint), averaged over seven tiers. Beat the HTML checkpoint and you're at 14%.\n\nYou finish by beating one checkpoint per tier: seven passes, at least 75 questions. The certificate needs a free account and lives at a public link (`\u002Fcertificates\u002FBP-…`) that anyone can open to check it. It states what it certifies: earned by every checkpoint.\n\n**Key concept:** finishing is defined by checkpoints alone, which is why a skipper and a completionist end up at the same finish line.\n\n## Under the hood: progress computed from quiz attempts\n\nIf you build learning tools, this is the part worth taking.\n\n**Progress is computed, not kept.** The map never reads your progress from a table; every load works it out again from the map and your best attempt at each quiz. The real `path-progress.ts` is covered by 13 Vitest cases. This simplified sketch follows the same rules:\n\n```ts\n\u002F\u002F A simplified sketch of path-progress.ts. The real one also places the \"You\"\n\u002F\u002F marker, follows the branch you started at a fork, and marks perfect scores.\ntype Stage = {\n  slug: string;\n  tier: number;              \u002F\u002F forks share a tier: Vue and React are both 4\n  unlockedBy: string[];      \u002F\u002F [] for HTML, [\"nuxt\", \"nextjs\"] for TypeScript\n  checkpoint: string;        \u002F\u002F the checkpoint's quiz id\n  levels: (string | null)[]; \u002F\u002F level quiz ids in order; null = \"Being written\"\n};\ntype Attempt = { quiz: string; score: number; maxScore: number };\n\nexport function pathProgress(stages: Stage[], attempts: Attempt[]) {\n  \u002F\u002F 1. Only your best attempt at each quiz counts, wherever you took it.\n  const best = new Map\u003Cstring, number>();\n  for (const { quiz, score, maxScore } of attempts) {\n    best.set(quiz, Math.max(best.get(quiz) ?? 0, (score \u002F maxScore) * 100));\n  }\n  const passed = (quiz: string) => (best.get(quiz) ?? 0) >= 70;\n\n  const ordered = [...stages].sort((a, b) => a.tier - b.tier);\n  const cleared = new Set\u003Cstring>();\n  const levels = new Map\u003Cstring, \"open\" | \"locked\" | \"cleared\">();\n\n  for (const stage of ordered) {\n    \u002F\u002F 2. A stage opens when ANY stage in its unlockedBy is cleared.\n    const open = stage.unlockedBy.length === 0 || stage.unlockedBy.some((s) => cleared.has(s));\n    \u002F\u002F 3. Its checkpoint, and nothing else, clears it.\n    if (open && passed(stage.checkpoint)) cleared.add(stage.slug);\n\n    \u002F\u002F 4. Levels open one at a time. A \"Being written\" level is skipped: it blocks nobody.\n    let previousPassed = true;\n    for (const quiz of stage.levels) {\n      if (quiz === null) continue;\n      const ok = passed(quiz);\n      levels.set(quiz, ok ? \"cleared\" : open && (previousPassed || cleared.has(stage.slug)) ? \"open\" : \"locked\");\n      previousPassed = ok;\n    }\n  }\n\n  \u002F\u002F 5. Percent along your route: each tier counts once, scored by its best\n  \u002F\u002F    stage, and a checkpoint is worth one level.\n  const tiers = [...new Set(ordered.map((s) => s.tier))];\n  const tierScore = (tier: number) =>\n    Math.max(...ordered.filter((s) => s.tier === tier).map((s) => {\n      if (cleared.has(s.slug)) return 1;\n      const written = s.levels.filter((q): q is string => q !== null);\n      return written.filter(passed).length \u002F (written.length + 1);\n    }));\n  const pct = Math.round((tiers.reduce((sum, t) => sum + tierScore(t), 0) \u002F tiers.length) * 100);\n\n  return { cleared, levels, pct };\n}\n```\n\nThat one decision pays for four features:\n\n- Guests get a working map with no account, because their quiz attempts were already rows, keyed to a cookie.\n- A quiz passed on the blog before the path existed counts the day the path does.\n- Adding a level needs no backfill.\n- Signing up keeps the run, because the run was only ever the attempts.\n\nA signed-in run also writes its latest percentage to an enrollment record, after the fact; the map never reads it back. The rest follows the same instinct:\n\n- **The map is the same for everyone; you are fetched.** The server renders the map without anyone's progress in it, so every visitor gets the same one. Your place on it arrives after hydration, and the default is a newcomer's view, so a first visit never shows padlocks flicking open.\n- **Looking costs nothing.** The progress endpoint never sets a cookie. The anonymous ID is only set when there is something to save, like a quiz attempt, so a crawler that only looks never gets one.\n- **Forks are data.** Each stage lists the stages that open it (`unlockedBy`), and the map draws one road per entry: the line, the fork and the join, from one rule.\n- **Checkpoints are assembled, not written.** Each release builds a stage's checkpoint from its level quizzes. They issue no certificate of their own; the path certificate is the proof.\n- **The catalogue is code.** A level whose article isn't written yet shows as \"Being written\" and blocks nobody. Publishing that article and its quiz under the planned slug is all the map needs; the next release picks it up.\n\n## When not to use it\n\nHere's when this path is the wrong tool, and what to use instead.\n\n- **You're starting from zero today.** The from-zero openers aren't written yet, and the first live level, the native `\u003Cdialog>`, assumes you've built a modal before. Start with the [MDN Curriculum](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fcurriculum\u002F), free and beginner-level, or [freeCodeCamp](https:\u002F\u002Fwww.freecodecamp.org\u002Flearn) for questions after every lesson, and come back for the checkpoints.\n- **You learn by building.** A quiz checks understanding, not whether you can ship a page. [The Odin Project](https:\u002F\u002Fwww.theodinproject.com\u002F) is free and project-based, and [Frontend Mentor](https:\u002F\u002Fwww.frontendmentor.io\u002Flearning-paths) gives you real designs to build, with feedback on the result.\n- **You need a closed-book credential.** These quizzes are open-book and unsupervised. freeCodeCamp's v9 certifications end in a closed-book exam and cost nothing.\n- **You learn from video.** Every level here is text. [Scrimba's Frontend Developer path](https:\u002F\u002Fscrimba.com\u002Ffrontend-path-c0j) is about 81 hours of interactive screencasts, on a paid plan.\n- **You want the whole map of the field.** This is one path through 44 articles; [roadmap.sh](https:\u002F\u002Froadmap.sh\u002Ffrontend) shows everything around it.\n- **You work in Angular or Svelte.** The forks are Vue and React only.\n- **You'll switch devices before signing up.** A guest run lives in one browser, and clearing its cookies loses it. Create the free account first.\n\n## FAQ\n\n### Is this frontend roadmap really free?\n\nYes. Every article, quiz and checkpoint on the path is free, and so is the account the certificate needs.\n\n### Do I need an account to start the frontend roadmap?\n\nNo. A guest run is saved on that device for a year. A free account carries it to every device, keeps what you did as a guest, and is required for the certificate.\n\n### How long does the frontend developer roadmap take?\n\nThe site estimates about 16 hours for one route; the read-time labels alone add up to 324 minutes on the Vue route and 302 on the React one. Skip everything you can, and the floor is seven checkpoints: 75 questions.\n\n### Should I learn Vue or React first?\n\nThe path doesn't rank them. Both open when you clear JavaScript, and TypeScript opens after either Nuxt or Next.js, so neither branch is a dead end. Pick the one your job, or the job you want, uses; your percentage only counts one branch.\n\n### Can I skip HTML and CSS?\n\nYes, if you can pass their checkpoints, 7 of 10 on each. Beat HTML's and CSS opens; beat CSS's and JavaScript opens. A stage only counts once it is open, so you skip in order.\n\n### What happens if I fail a checkpoint?\n\nNothing is lost. The review explains every question you missed, and you can retake it straight away: each attempt is a new random draw, with no cooldown and no attempt limit, and your best result counts.\n\n### Is the frontend developer certificate worth anything?\n\nIt records one thing: that the named account passed one checkpoint per stage at 70% or better, open-book, at least 75 questions in all. Anyone can open its public link to check it. It is not a closed-book credential; freeCodeCamp's is the free one built for that. Treat it as a receipt, not a diploma.\n\n## Cheat sheet: the path at a glance\n\nThe numbers below come from the live map as of 14 October 2026; \"live levels\" counts written levels only.\n\n| Stage | Live levels | Checkpoint | Two example levels |\n|---|---|---|---|\n| 1 · HTML | 4 | 10 questions, 7 to pass | Native `\u003Cdialog>`, The inert attribute |\n| 2 · CSS | 5 | 10 questions, 7 to pass | Specificity with `:where()`, Viewport units: dvh, svh, lvh |\n| 3 · JavaScript | 6 | 10 questions, 7 to pass | Closures, The event loop |\n| 4 · Vue (or React) | 6 | 10 questions, 7 to pass | ref vs reactive, Composables & shared state |\n| 4 · React (or Vue) | 5 | 10 questions, 7 to pass | Re-render vs remount, React Compiler |\n| 5 · Nuxt (after Vue) | 5 | 10 questions, 7 to pass | useAsyncData keys, Hydration mismatches |\n| 5 · Next.js (after React) | 5 | 10 questions, 7 to pass | Cache Components, proxy.ts |\n| 6 · TypeScript | 3 | 10 questions, 7 to pass | Generics, `satisfies` |\n| 7 · Ship it | 5 | 15 questions, 11 to pass | Core Web Vitals, Web Workers |\n\nThat's 44 written levels across both forks, 34 on the Vue route and 33 on the React route, plus 7 being written.\n\nThe rules in five lines:\n\n```text\nlevel       article + quiz (6–9 questions). Pass at 70%; your best attempt counts.\norder       Levels open one at a time. A locked article is still free to read.\ncheckpoint  10 random questions from the stage (15 for Ship it). 70% clears it.\nskip        A checkpoint is open as soon as its stage is. Beat it early to skip.\nfinish      One checkpoint per tier. Certificate: free account, public link.\n```\n\n## Key takeaways\n\n- A tick you give yourself records what you read, not what you can answer. Compute progress from answers.\n- In Roediger and Karpicke's 2006 study, re-reading won at five minutes; testing won at two days and at a week, with no feedback.\n- Skipping is evidence arriving early: pass the check, and the reading was optional.\n- Building a learning tool? Store the attempts and derive everything else.\n\nBack to Sunday night, and the roadmap that only knows what you ticked. Spend the first evening on checkpoints instead of chapters. The stages you pass are done, and the ones you fail hand you every missed question, explained: a better reading list than a column of ticks ever was.\n\nWhich checkpoint do you think you could pass without reading anything, and which one would surprise you?\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":47,"canonical":528,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ffrontend-roadmap-you-have-to-pass","01a13bf6-f5c6-7599-9340-f4084996139c",{"id":531,"locked":18},"01a13bf6-f626-724f-994a-b95dc2c245b9",[],[534],{"locale":13,"slug":46},[536,538,541,543,547,551,555,558,562,566,569,573],{"slug":65,"name":66,"articles":537},139,{"slug":61,"name":539,"articles":540},"Javascript",113,{"slug":68,"name":69,"articles":542},84,{"slug":544,"name":545,"articles":546},"tutorial","Tutorial",53,{"slug":548,"name":549,"articles":550},"css","Css",42,{"slug":552,"name":553,"articles":554},"performance","Performance",21,{"slug":556,"name":557,"articles":51},"typescript","Typescript",{"slug":559,"name":560,"articles":561},"react","React",17,{"slug":563,"name":564,"articles":565},"browser","Browser",13,{"slug":567,"name":568,"articles":53},"node","Node",{"slug":570,"name":571,"articles":572},"programming","Programming",10,{"slug":574,"name":575,"articles":576},"accessibility","Accessibility",9,[],{"items":579,"meta":708},[580,590,609,628,649,669,687],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":565,"likeCount":19,"commentCount":19,"author":581,"vertical":582,"topic":583,"tags":584,"_count":589,"playground":48,"hasQuiz":18,"hasPlayground":18},{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[585,586,587,588],{"slug":65,"name":66,"color":48},{"slug":68,"name":69,"color":48},{"slug":71,"name":72,"color":48},{"slug":74,"name":75,"color":48},{"assessments":19},{"id":591,"slug":592,"title":593,"subtitle":48,"excerpt":594,"coverUrl":595,"locale":13,"readingMinutes":596,"publishedAt":597,"viewCount":598,"likeCount":19,"commentCount":19,"author":599,"vertical":600,"topic":601,"tags":602,"_count":607,"playground":608,"hasQuiz":17,"hasPlayground":17},"01a11f48-c748-7368-a51b-ccd1d636ad0c","performanceobserver-core-web-vitals","You Installed a Library for Numbers PerformanceObserver Already Has.","The web-vitals npm package gets credit for data your browser already computes. Here's what three PerformanceObserver calls give you for free — and the one piece of math it quietly saves you from getting wrong.","\u002Fmedia\u002Fcovers\u002Fperformanceobserver-core-web-vitals.png",7,"2026-10-14T06:10:28.030Z",92,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[603,604,605,606],{"slug":61,"name":539,"color":48},{"slug":552,"name":553,"color":48},{"slug":65,"name":66,"color":48},{"slug":570,"name":571,"color":48},{"assessments":525},{"slug":592},{"id":610,"slug":611,"title":612,"subtitle":48,"excerpt":613,"coverUrl":614,"locale":13,"readingMinutes":615,"publishedAt":616,"viewCount":617,"likeCount":19,"commentCount":19,"author":618,"vertical":619,"topic":620,"tags":621,"_count":626,"playground":627,"hasQuiz":17,"hasPlayground":17},"01a11a22-57e3-710c-8787-8b86669fb1c9","tab-leader-election-weblocks-broadcastchannel","Your Tabs Each Open Their Own WebSocket. navigator.locks Elects One.","Six open tabs means six live connections to your server, six duplicate pushes, six timers draining the battery. navigator.locks elects a single leader tab to hold the real connection — the rest just listen.","\u002Fmedia\u002Fcovers\u002Ftab-leader-election-weblocks-broadcastchannel.png",6,"2026-10-13T06:11:00.980Z",127,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[622,623,624,625],{"slug":61,"name":539,"color":48},{"slug":65,"name":66,"color":48},{"slug":68,"name":69,"color":48},{"slug":552,"name":553,"color":48},{"assessments":525},{"slug":611},{"id":629,"slug":630,"title":631,"subtitle":48,"excerpt":632,"coverUrl":633,"locale":13,"readingMinutes":634,"publishedAt":635,"viewCount":636,"likeCount":19,"commentCount":19,"author":637,"vertical":638,"topic":639,"tags":642,"_count":647,"playground":648,"hasQuiz":17,"hasPlayground":17},"01a120b9-d611-74ef-ad15-a267677c3251","nextjs-weekly-image-priority-quality-changes","Next.js 16 Image: priority Deprecated, quality Now Allowlisted","Next.js 16 quietly deprecated Image's priority for preload and restricts quality to an allowlist (default: 75). What breaks, plus a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnextjs-weekly-image-priority-quality-changes.png",15,"2026-10-13T06:10:29.815Z",216,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":640,"name":641},"nextjs","Nextjs",[643,644,645,646],{"slug":640,"name":641,"color":48},{"slug":559,"name":560,"color":48},{"slug":65,"name":66,"color":48},{"slug":544,"name":545,"color":48},{"assessments":525},{"slug":630},{"id":650,"slug":651,"title":652,"subtitle":48,"excerpt":653,"coverUrl":654,"locale":13,"readingMinutes":634,"publishedAt":655,"viewCount":656,"likeCount":19,"commentCount":19,"author":657,"vertical":658,"topic":659,"tags":662,"_count":667,"playground":668,"hasQuiz":17,"hasPlayground":17},"01a0ec73-2e41-711f-9f8a-b6c9bae1e054","vue-weekly-watch-vs-watcheffect","Vue watch vs watchEffect: The Complete Guide","Vue's watch() and watchEffect() look interchangeable but track dependencies, run timing, and cleanup differently. The exact rules, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-watch-vs-watcheffect.png","2026-10-12T06:10:54.504Z",220,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":660,"name":661},"vue","Vue",[663,664,665,666],{"slug":660,"name":661,"color":48},{"slug":61,"name":539,"color":48},{"slug":544,"name":545,"color":48},{"slug":65,"name":66,"color":48},{"assessments":525},{"slug":651},{"id":670,"slug":671,"title":672,"subtitle":48,"excerpt":673,"coverUrl":674,"locale":13,"readingMinutes":615,"publishedAt":675,"viewCount":676,"likeCount":19,"commentCount":19,"author":677,"vertical":678,"topic":679,"tags":680,"_count":685,"playground":686,"hasQuiz":17,"hasPlayground":17},"01a114fb-f147-727a-a137-53c8ce35f6bf","service-worker-stale-cache-deploy","I Shipped a Fix. My Service Worker Kept Serving the Bug.","A critical bug fix went out, and support tickets kept coming in from people who swore they'd refreshed. The code was fine. The service worker's cache-first strategy was doing its job perfectly — which was the whole problem.","\u002Fmedia\u002Fcovers\u002Fservice-worker-stale-cache-deploy.png","2026-10-12T06:10:22.830Z",373,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[681,682,683,684],{"slug":61,"name":539,"color":48},{"slug":65,"name":66,"color":48},{"slug":563,"name":564,"color":48},{"slug":552,"name":553,"color":48},{"assessments":525},{"slug":671},{"id":688,"slug":689,"title":690,"subtitle":48,"excerpt":691,"coverUrl":692,"locale":13,"readingMinutes":693,"publishedAt":694,"viewCount":695,"likeCount":19,"commentCount":19,"author":696,"vertical":697,"topic":698,"tags":701,"_count":706,"playground":707,"hasQuiz":17,"hasPlayground":17},"01a11d02-8b28-774b-9f36-fb66bece8e50","nuxt-weekly-4-migration-breaking-changes","Nuxt 4 Migration: The Breaking Changes That Actually Bite","Nuxt 3 reached end-of-life on July 31, 2026. Here's exactly what breaks moving to Nuxt 4 — directories, data fetching, TypeScript — plus a cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-4-migration-breaking-changes.png",14,"2026-10-11T12:26:26.492Z",272,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":699,"name":700},"nuxt","Nuxt",[702,703,704,705],{"slug":699,"name":700,"color":48},{"slug":61,"name":539,"color":48},{"slug":544,"name":545,"color":48},{"slug":65,"name":66,"color":48},{"assessments":525},{"slug":689},{"page":525,"perPage":596,"total":537,"totalPages":709},20,"\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Status\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"done\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"learning\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"skipped\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"pending\"\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> progress\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Status\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \"semantic-html\"\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"done\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>  \u003Cspan class=\"shj-cmnt\">\u002F\u002F ticked two years ago\n\u003C\u002Fspan>  \u003Cspan class=\"shj-str\">\"event-loop\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"done\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>     \u003Cspan class=\"shj-cmnt\">\u002F\u002F read the article, felt fine\n\u003C\u002Fspan>  promises\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"skipped\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>      \u003Cspan class=\"shj-cmnt\">\u002F\u002F \"I use them every day\"\n\u003C\u002Fspan>  specificity\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"pending\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003Cdiv>25\u003C\u002Fdiv>\u003Cdiv>26\u003C\u002Fdiv>\u003Cdiv>27\u003C\u002Fdiv>\u003Cdiv>28\u003C\u002Fdiv>\u003Cdiv>29\u003C\u002Fdiv>\u003Cdiv>30\u003C\u002Fdiv>\u003Cdiv>31\u003C\u002Fdiv>\u003Cdiv>32\u003C\u002Fdiv>\u003Cdiv>33\u003C\u002Fdiv>\u003Cdiv>34\u003C\u002Fdiv>\u003Cdiv>35\u003C\u002Fdiv>\u003Cdiv>36\u003C\u002Fdiv>\u003Cdiv>37\u003C\u002Fdiv>\u003Cdiv>38\u003C\u002Fdiv>\u003Cdiv>39\u003C\u002Fdiv>\u003Cdiv>40\u003C\u002Fdiv>\u003Cdiv>41\u003C\u002Fdiv>\u003Cdiv>42\u003C\u002Fdiv>\u003Cdiv>43\u003C\u002Fdiv>\u003Cdiv>44\u003C\u002Fdiv>\u003Cdiv>45\u003C\u002Fdiv>\u003Cdiv>46\u003C\u002Fdiv>\u003Cdiv>47\u003C\u002Fdiv>\u003Cdiv>48\u003C\u002Fdiv>\u003Cdiv>49\u003C\u002Fdiv>\u003Cdiv>50\u003C\u002Fdiv>\u003Cdiv>51\u003C\u002Fdiv>\u003Cdiv>52\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F A simplified sketch of path-progress.ts. The real one also places the \"You\"\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F marker, follows the branch you started at a fork, and marks perfect scores.\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Stage\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  slug\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>;\n  tier\u003Cspan class=\"shj-type\">: number\u003C\u002Fspan>;              \u003Cspan class=\"shj-cmnt\">\u002F\u002F forks share a tier: Vue and React are both 4\n\u003C\u002Fspan>  unlockedBy\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;      \u003Cspan class=\"shj-cmnt\">\u002F\u002F [] for HTML, [\"nuxt\", \"nextjs\"] for TypeScript\n\u003C\u002Fspan>  checkpoint\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>;        \u003Cspan class=\"shj-cmnt\">\u002F\u002F the checkpoint's quiz id\n\u003C\u002Fspan>  levels\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>string \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F level quiz ids in order; null = \"Being written\"\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Attempt\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> quiz\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>; score\u003Cspan class=\"shj-type\">: number\u003C\u002Fspan>; maxScore\u003Cspan class=\"shj-type\">: number\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">pathProgress\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>stages\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Stage\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> attempts\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Attempt\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-cmnt\">\u002F\u002F 1. Only your best attempt at each quiz counts, wherever you took it.\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> best \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Map\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> number\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">for\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> quiz\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> score\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> maxScore \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> attempts\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    best\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>quiz\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Math\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">max\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>best\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">get\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>quiz\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>score \u003Cspan class=\"shj-oper\">\u002F\u003C\u002Fspan> maxScore\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> passed \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>quiz\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>best\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">get\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>quiz\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">&gt;=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">70\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> ordered \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>stages\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">sort\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>a\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> b\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> a\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>tier \u003Cspan class=\"shj-oper\">-\u003C\u002Fspan> b\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>tier\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> cleared \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Set\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> levels \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Map\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"open\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"locked\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"cleared\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">for\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> stage \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> ordered\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-cmnt\">\u002F\u002F 2. A stage opens when ANY stage in its unlockedBy is cleared.\n\u003C\u002Fspan>    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> open \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> stage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>unlockedBy\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>length \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">||\u003C\u002Fspan> stage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>unlockedBy\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">some\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> cleared\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">has\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-cmnt\">\u002F\u002F 3. Its checkpoint, and nothing else, clears it.\n\u003C\u002Fspan>    \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>open \u003Cspan class=\"shj-oper\">&#38;&#38;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">passed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>stage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>checkpoint\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> cleared\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">add\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>stage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>slug\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n    \u003Cspan class=\"shj-cmnt\">\u002F\u002F 4. Levels open one at a time. A \"Being written\" level is skipped: it blocks nobody.\n\u003C\u002Fspan>    \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> previousPassed \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-kwd\">for\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> quiz \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> stage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>levels\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>quiz \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">continue\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> ok \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">passed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>quiz\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      levels\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>quiz\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> ok \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"cleared\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> open \u003Cspan class=\"shj-oper\">&#38;&#38;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>previousPassed \u003Cspan class=\"shj-oper\">||\u003C\u002Fspan> cleared\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">has\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>stage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>slug\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"open\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"locked\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      previousPassed \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> ok;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n  \u003Cspan class=\"shj-cmnt\">\u002F\u002F 5. Percent along your route: each tier counts once, scored by its best\n\u003C\u002Fspan>  \u003Cspan class=\"shj-cmnt\">\u002F\u002F    stage, and a checkpoint is worth one level.\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> tiers \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>ordered\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">map\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> s\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>tier\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> tierScore \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>tier\u003Cspan class=\"shj-type\">: number\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan>\n    \u003Cspan class=\"shj-class\">Math\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">max\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>ordered\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">filter\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> s\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>tier \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> tier\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">map\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>cleared\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">has\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>slug\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> written \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> s\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>levels\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">filter\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> q is string \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> q \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> written\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">filter\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>passed\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>length \u003Cspan class=\"shj-oper\">\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>written\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>length \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> pct \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Math\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">round\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>tiers\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">reduce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>sum\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> t\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> sum \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> \u003Cspan class=\"shj-func\">tierScore\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>t\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F\u003C\u002Fspan> tiers\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>length\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> cleared\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> levels\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> pct \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-text shj-multiline\" data-lang=\"text\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">level       article + quiz (6–9 questions). Pass at 70%; your best attempt counts.\norder       Levels open one at a time. A locked article is still free to read.\ncheckpoint  10 random questions from the stage (15 for Ship it). 70% clears it.\nskip        A checkpoint is open as soon as its stage is. Beat it early to skip.\nfinish      One checkpoint per tier. Certificate: free account, public link.\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":714},[]]