Funcționalități avansate într-un blog modern: Comentarii, Paginare, Upload și SEO

  • Doru Bulubasa
  • 23 June 2025

După ce ai implementat funcționalitățile de bază — articole, autentificare, afișare listă — e timpul să trecem la lucruri serioase: funcționalități avansate care îmbunătățesc experiența utilizatorului și profesionalizează aplicația.

În acest articol vom acoperi:

  • Comentarii și relații între entități (Post ↔ Comments)

  • Paginare, căutare și filtrare articole

  • Upload imagine cover pentru articol (multipart/form-data)

  • SEO: SSR vs SPA – decizii pentru un blog modern


💬 1. Comentarii și relații între entități (Post ↔ Comments)

✅ Modelare în backend

public class Post
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public ICollection<Comment> Comments { get; set; }
}

public class Comment
{
    public Guid Id { get; set; }
    public string AuthorName { get; set; }
    public string Text { get; set; }
    public DateTime CreatedAt { get; set; }

    public Guid PostId { get; set; }
    public Post Post { get; set; }
}

  • Comentariile sunt legate prin PostId

  • Folosește Include(p => p.Comments) pentru a încărca comentariile

✅ Endpointuri

[HttpGet("{id}")]
public async Task<IActionResult> GetPostWithComments(Guid id)
{
    var post = await _context.Posts
        .Include(p => p.Comments)
        .FirstOrDefaultAsync(p => p.Id == id);

    return Ok(post);
}

[HttpPost("{postId}/comments")]
public async Task<IActionResult> AddComment(Guid postId, [FromBody] CommentDto dto)
{
    var comment = new Comment { Text = dto.Text, PostId = postId, CreatedAt = DateTime.UtcNow };
    _context.Comments.Add(comment);
    await _context.SaveChangesAsync();
    return Ok();
}

✅ React

const PostPage = () => {
  const [post, setPost] = useState<Post | null>(null);

  useEffect(() => {
    fetch(`/api/posts/${id}`).then(res => res.json()).then(setPost);
  }, []);

  return (
    <>
      <h1>{post?.title}</h1>
      <p>{post?.content}</p>
      <h3>Comentarii</h3>
      <ul>
        {post?.comments.map(c => (
          <li key={c.id}><strong>{c.authorName}</strong>: {c.text}</li>
        ))}
      </ul>
    </>
  );
};


🔍 2. Paginare, căutare și filtrare articole

✅ Backend

[HttpGet]
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] string? search = null)
{
    var query = _context.Posts.AsQueryable();

    if (!string.IsNullOrEmpty(search))
        query = query.Where(p => p.Title.Contains(search));

    var pageSize = 10;
    var items = await query
        .OrderByDescending(p => p.CreatedAt)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();

    return Ok(items);
}

✅ React – componente

const [search, setSearch] = useState('');
const [page, setPage] = useState(1);

useEffect(() => {
  fetch(`/api/posts?page=${page}&search=${search}`)
    .then(res => res.json())
    .then(setPosts);
}, [page, search]);

✅ Poți adăuga componente ca:

  • <Pagination /> (cu butoane + număr pagină)

  • <input type="search" /> pentru titlu/conținut


🖼️ 3. Upload imagine cover pentru articol

✅ Backend (multipart/form-data)

[HttpPost]
public async Task<IActionResult> Upload([FromForm] CreatePostWithImageDto dto)
{
    var file = dto.Image;
    var fileName = Guid.NewGuid() + Path.GetExtension(file.FileName);
    var path = Path.Combine("uploads", fileName);

    using var stream = new FileStream(path, FileMode.Create);
    await file.CopyToAsync(stream);

    var post = new Post { Title = dto.Title, Content = dto.Content, ImageUrl = $"/uploads/{fileName}" };
    _context.Posts.Add(post);
    await _context.SaveChangesAsync();
    return Ok();
}

✅ React + FormData

const handleSubmit = async (e) => {
  e.preventDefault();
  const data = new FormData();
  data.append("title", title);
  data.append("content", content);
  data.append("image", file);

  await fetch("/api/posts", {
    method: "POST",
    body: data
  });
};


🌐 4. SEO și rendering: SSR vs SPA

SPA (Single Page Application)

Avantaje:

  • Încărcare rapidă după primul request

  • Ușor de dezvoltat

  • Bun pentru dashboard-uri, aplicații interne

Dezavantaje:

  • Nu e ideal pentru crawlere (Googlebot etc.)

  • Fără prerendering, titlurile și meta tag-urile nu sunt vizibile

SSR (Server-Side Rendering) – opțional în viitor

Poți migra la:

  • Next.js (React SSR/SSG)

  • Prerender.io + caching proxy

  • Sau poți genera meta tag-uri dinamice în backend pentru anumite URL-uri cu aspnetcore + blazor-server.


✅ Concluzie

Aceste funcționalități transformă un blog simplu într-o aplicație completă și scalabilă:

✅ Comentarii și entități conectate

✅ Paginare și filtre pentru UX optim

✅ Upload imagini cover în mod elegant

✅ Gândire SEO pentru vizibilitate pe web

🤖 Întreabă AI despre acest articol

AI Răspuns generat de AI pe baza acestui articol.
AI scrie răspunsul…

Scrie un comentariu

Adresa de mail nu va fi publicata. Campurile obligatorii sunt marcate cu *