[{"data":1,"prerenderedAt":225},["ShallowReactive",2],{"blog:page:\u002Fvi\u002Fblog\u002Furl-model":3,"blog:articles:vi":166,"blog:translations:\u002Fvi\u002Fblog\u002Furl-model":222},{"id":4,"title":5,"author":6,"body":7,"cover":153,"coverAlt":154,"date":155,"description":156,"extension":157,"featured":158,"meta":159,"navigation":158,"path":124,"seo":160,"stem":161,"tags":162,"__hash__":165},"blog\u002Fvi\u002Fblog\u002F1.url-model\u002Findex.md","Một mô hình URL cho mọi trang public","John Martin",{"type":8,"value":9,"toc":147},"minimark",[10,14,23,33,36,43,48,95,99,102,108,111,115,126,132,135],[11,12,5],"h1",{"id":13},"một-mô-hình-url-cho-mọi-trang-public",[15,16,17,18,22],"p",{},"Mọi trang trên ",[19,20,21],"code",{},"ecoma.io"," đều nằm dưới một hình dạng duy nhất:",[24,25,31],"pre",{"className":26,"code":28,"language":29,"meta":30},[27],"language-text","\u002F\u003Clocale>\u002F\u003Cmount>\u002F\u003Cpath>\n","text","",[19,32,28],{"__ignoreMap":30},[15,34,35],{},"Đó không phải một quy ước mà ta hy vọng các team tự tuân theo. Nó được parse,\nbị từ chối và được dựng bởi hai thư viện nhỏ, nên một trang không thể lặng lẽ\nlệch sang hình dạng mà phần còn lại của nền tảng không hiểu.",[15,37,38],{},[39,40],"img",{"alt":41,"src":42},"Sơ đồ một website public gồm ba tầng xếp chồng, từ trang xuống phân phối tĩnh","\u002Fassets\u002Fpublic-web-layers.svg",[44,45,47],"h2",{"id":46},"ba-segment","Ba segment",[49,50,51,67,89],"ul",{},[52,53,54,58,59,62,63,66],"li",{},[55,56,57],"strong",{},"locale"," đứng đầu, luôn luôn: ",[19,60,61],{},"\u002Fen\u002F..."," hoặc ",[19,64,65],{},"\u002Fvi\u002F...",". Không có biến thể\nkhông prefix, nên một URL không bao giờ phải tự đoán ngôn ngữ của nó.",[52,68,69,72,73,76,77,80,81,84,85,88],{},[55,70,71],{},"mount"," gọi tên bề mặt: ",[19,74,75],{},"blog",", ",[19,78,79],{},"docs",". Mount match trên biên segment đầy\nđủ - ",[19,82,83],{},"\u002Fen\u002Fblogging"," ",[55,86,87],{},"không phải"," blog.",[52,90,91,94],{},[55,92,93],{},"path"," là mọi thứ sau mount, và nó thuộc về app sở hữu mount đó.",[44,96,98],{"id":97},"nghiêm-ngặt-là-một-tính-năng","Nghiêm ngặt là một tính năng",[15,100,101],{},"Parser không sửa chữa input. Trailing slash, segment rỗng, hay locale lạ đều\nbị từ chối kèm reason có kiểu:",[24,103,106],{"className":104,"code":105,"language":29,"meta":30},[27],"\u002Fen        -> ok\n\u002Fen\u002Fdocs\u002F  -> bị chặn: trailing slash\n\u002FEN\u002Fdocs   -> bị chặn: locale không hỗ trợ\n\u002Fen\u002F\u002Fdocs  -> bị chặn: segment rỗng\n",[19,107,105],{"__ignoreMap":30},[15,109,110],{},"\"Sửa giúp\" nghe có vẻ tử tế nhưng âm thầm có hại: một URL \"chạy được\" ở dạng\nkhông canonical sẽ bị index, bị cache và được chia sẻ đúng ở dạng đó. Từ chối\ngiữ cho mỗi trang đúng một địa chỉ canonical.",[44,112,114],{"id":113},"slug-sống-sót-qua-bản-dịch","Slug sống sót qua bản dịch",[15,116,117,118,121,122,125],{},"Một article giữ nguyên slug ở cả hai ngôn ngữ. ",[19,119,120],{},"\u002Fen\u002Fblog\u002Furl-model"," và\n",[19,123,124],{},"\u002Fvi\u002Fblog\u002Furl-model"," là cùng một resource mang hai ngôn ngữ, nên locale\nswitcher chuyển reader qua lại giữa chúng mà không cần bảng tra cứu.",[15,127,128],{},[39,129],{"alt":130,"src":131},"Một slug article phân nhánh thành path tiếng Anh và path tiếng Việt","\u002Fassets\u002Ftwo-locales-one-page.svg",[133,134],"hr",{},[15,136,137],{},[138,139,140,141,146],"em",{},"Liên quan: bề mặt docs mô tả cùng mô hình này sâu hơn - xem\n",[142,143,145],"a",{"href":144},"\u002Fen\u002Fblog\u002Fbuild-time-content","Các tầng của public web"," để biết content đến trang\ntheo đường nào.",{"title":30,"searchDepth":148,"depth":148,"links":149},2,[150,151,152],{"id":46,"depth":148,"text":47},{"id":97,"depth":148,"text":98},{"id":113,"depth":148,"text":114},"\u002Fassets\u002Fcover-url-model.svg","Minh họa bìa: một thanh địa chỉ tách thành ba segment locale, mount và path","2026-09-02","\u002Flocale\u002Fmount\u002Fpath giữ cho mọi trang public đoán được - và vì sao ta ràng buộc nó thay vì hy vọng nó được tuân thủ.","md",true,{},{"title":5,"description":156},"vi\u002Fblog\u002F1.url-model\u002Findex",[163,164],"architecture","urls","bjTzEldXLDA_iyKpoijey3x36FGEoduZdRetUPwdLoc",[167,176,188,200,211,219],{"path":168,"stem":169,"title":170,"description":171,"date":172,"author":6,"tags":173,"featured":175},"\u002Fvi\u002Fblog\u002Fdemo-content-note","vi\u002Fblog\u002F6.demo-content-note\u002Findex","Blog này là gì (và không phải gì)","Đây là một vertical slice demo - layout thật, pipeline thật, content editorial một cách có chủ ý.","2026-10-07",[174],"meta",false,{"path":177,"stem":178,"title":179,"description":180,"date":181,"author":6,"tags":182,"featured":175,"cover":185},"\u002Fvi\u002Fblog\u002Freading-first-design","vi\u002Fblog\u002F5.reading-first-design\u002Findex","Trang đọc bài không phải trang tài liệu","Vì sao layout article của blog cố tình khác layout docs - phân cấp thông tin, bề rộng cột, và sự tiết chế.","2026-09-30",[183,184],"design","typography",{"src":186,"alt":187},"\u002Fassets\u002Fcover-reading-first.svg","Minh họa bìa: bố cục một cột hẹp được chọn thay vì bố cục nhiều cột rối rắm",{"path":189,"stem":190,"title":191,"description":192,"date":193,"author":6,"tags":194,"featured":175,"cover":197},"\u002Fvi\u002Fblog\u002Fstatic-deployment","vi\u002Fblog\u002F4.static-deployment\u002Findex","Deploy một blog như static assets","Không Worker script, không database binding - toàn bộ deployment là một thư mục file và một trang 404.","2026-09-23",[195,196],"deployment","cloudflare",{"src":198,"alt":199},"\u002Fassets\u002Fcover-static-deployment.svg","Minh họa bìa: thư mục các tệp HTML và SVG chảy ra mạng lưới edge CDN",{"path":201,"stem":202,"title":203,"description":204,"date":205,"author":6,"tags":206,"featured":175,"cover":209},"\u002Fvi\u002Fblog\u002Flocale-switching","vi\u002Fblog\u002F3.locale-switching\u002Findex","Locale switching không bao giờ nói dối","Language switcher chỉ được offer thứ tồn tại. Đây là cách danh sách link được suy ra từ content thật.","2026-09-16",[207,208],"i18n","ux",{"src":131,"alt":210},"Minh họa bìa: hai đường dẫn ngôn ngữ hội tụ về cùng một bố cục trang",{"path":212,"stem":213,"title":214,"description":215,"date":216,"author":6,"tags":217,"featured":175},"\u002Fvi\u002Fblog\u002Fbuild-time-content","vi\u002Fblog\u002F2.build-time-content\u002Findex","Content được resolve ở build time","Vì sao blog này ship dưới dạng file prerender thay vì một content API - và điều đó mua được gì.","2026-09-09",[163,218],"static-sites",{"path":124,"stem":161,"title":5,"description":156,"date":155,"author":6,"tags":220,"featured":158,"cover":221},[163,164],{"src":153,"alt":154},[223,224],"en","vi",1791809694164]